package main import ( "encoding/json" "testing" "verstak/internal/core/activity" "verstak/internal/core/util" "verstak/internal/core/worklog" ) // TestAcceptSuggestionWithEndToEnd verifies the full chain: // 1. Create a node // 2. Create activity events for that node // 3. Accept a suggestion with those event IDs // 4. Verify worklog_entry_events contains exactly those events // 5. Verify GetWorklogEntryEvents returns the linked events func TestAcceptSuggestionWithEndToEnd(t *testing.T) { app, _ := setupTestApp(t) // 1. Create a node n, err := app.CreateNodeFromTemplate("", "Test Suggestion", "folder.default") if err != nil { t.Fatalf("create node: %v", err) } // 2. Create activity events for the node. // We need to insert events with today's timestamp so GetSuggestions picks them up. // But to avoid time dependency, we also directly accept with known event IDs. eid1 := insertTestEvent(t, app, n.ID, activity.TypeNoteCreated, "note", "note1", "Test note 1") eid2 := insertTestEvent(t, app, n.ID, activity.TypeNoteUpdated, "note", "note1", "Test note 1 updated") eid3 := insertTestEvent(t, app, n.ID, activity.TypeFileAdded, "file", "file1", "Test file") eventIDs := []string{eid1, eid2, eid3} eventIDsJSON, _ := json.Marshal(eventIDs) // 3. Accept suggestion with these event IDs dto, err := app.AcceptSuggestionWith(n.ID, "Работа с заметками", 15, "", string(eventIDsJSON)) if err != nil { t.Fatalf("AcceptSuggestionWith: %v", err) } if dto.ID == "" { t.Fatal("AcceptSuggestionWith returned empty entry ID") } // 4. Verify worklog_entry_events count var linkCount int err = app.db.QueryRow( `SELECT COUNT(*) FROM worklog_entry_events WHERE entry_id = ?`, dto.ID).Scan(&linkCount) if err != nil { t.Fatalf("count worklog_entry_events: %v", err) } if linkCount != 3 { t.Errorf("worklog_entry_events count = %d, want 3", linkCount) } // 5. Verify JOIN with activity_events var joinCount int err = app.db.QueryRow( `SELECT COUNT(*) FROM worklog_entry_events wle JOIN activity_events ae ON ae.id = wle.event_id WHERE wle.entry_id = ?`, dto.ID).Scan(&joinCount) if err != nil { t.Fatalf("join count: %v", err) } if joinCount != 3 { t.Errorf("JOIN count = %d, want 3", joinCount) } // 6. Call GetWorklogEntryEvents and verify 3 events events, err := app.GetWorklogEntryEvents(dto.ID) if err != nil { t.Fatalf("GetWorklogEntryEvents: %v", err) } if len(events) != 3 { t.Errorf("GetWorklogEntryEvents returned %d events, want 3", len(events)) } // 7. Verify the returned event IDs match returnedIDs := make(map[string]bool, len(events)) for _, ev := range events { returnedIDs[ev.ID] = true } for _, want := range eventIDs { if !returnedIDs[want] { t.Errorf("event %s not found in GetWorklogEntryEvents result", want) } } // 8. Verify source is 'suggestion' var source string err = app.db.QueryRow( `SELECT source FROM worklog_entries WHERE id = ?`, dto.ID).Scan(&source) if err != nil { t.Fatalf("get source: %v", err) } if source != worklog.SourceSuggestion { t.Errorf("source = %q, want %q", source, worklog.SourceSuggestion) } } // insertTestEvent inserts an activity event and returns its ID. func insertTestEvent(t *testing.T, app *App, nodeID, eventType, targetType, targetID, title string) string { t.Helper() id := util.UUID7() now := "2026-06-03T12:00:00Z" _, err := app.db.Exec( `INSERT INTO activity_events(id,node_id,event_type,target_type,target_id,target_path,title,metadata,created_at) VALUES(?,?,?,?,?,?,?,?,?)`, id, nodeID, eventType, targetType, targetID, "", title, "{}", now) if err != nil { t.Fatalf("insert event: %v", err) } return id }