diff options
Diffstat (limited to 'cmds/export_test.go')
| -rw-r--r-- | cmds/export_test.go | 120 |
1 files changed, 114 insertions, 6 deletions
diff --git a/cmds/export_test.go b/cmds/export_test.go index 25eadbb..0be1ac3 100644 --- a/cmds/export_test.go +++ b/cmds/export_test.go @@ -218,6 +218,105 @@ func TestExportURLDedup(t *testing.T) { } } +// Read-then-starred: an entry sampled as a negative, starred later, must flip +// to positive (identity kept, captured_at refreshed) rather than be dropped as +// a duplicate — the star is the signal, and a stale negative row is worse than +// no row at all. +func TestExportRelabelsStarredNegative(t *testing.T) { + p := &pagedServer{starred: makeEntries(1, 1000, true, 7)} + dir := t.TempDir() + labelsPath := filepath.Join(dir, "labels.jsonl") + + old := time.Now().Add(-48 * time.Hour).UTC() + pre := core.LabelRow{ + EntryID: 1000, URL: "https://example.com/post/1000", Title: "Title 1000", + FeedID: 7, FeedTitle: "Fixture Feed", Label: core.LabelNeg, + CapturedAt: old, + } + if err := core.AppendLabels(labelsPath, []core.LabelRow{pre}); err != nil { + t.Fatal(err) + } + + out := runExportAgainst(t, p, dir) + if !strings.Contains(out, "relabeled=1") || !strings.Contains(out, "appended=0") { + t.Errorf("expected relabeled=1 appended=0, got:\n%s", out) + } + + labels, err := core.ReadLabels(labelsPath) + if err != nil { + t.Fatal(err) + } + if len(labels) != 1 { + t.Fatalf("relabel must rewrite in place, got %d rows", len(labels)) + } + got := labels[0] + if got.Label != core.LabelPos { + t.Errorf("label = %q, want pos", got.Label) + } + if got.EntryID != 1000 || got.Title != "Title 1000" || got.FeedID != 7 { + t.Errorf("relabel must keep the row's identity, got %+v", got) + } + if !got.CapturedAt.After(old) { + t.Errorf("captured_at = %v, want refreshed past %v", got.CapturedAt, old) + } + + // The pre-rewrite file is preserved as a backup. + bak, err := core.ReadLabels(labelsPath + ".bak") + if err != nil { + t.Fatalf("read backup: %v", err) + } + if len(bak) != 1 || bak[0].Label != core.LabelNeg { + t.Errorf("backup should hold the pre-flip negative row, got %+v", bak) + } + + body, err := os.ReadFile(filepath.Join(dir, "export_run.json")) + if err != nil { + t.Fatal(err) + } + var rs runSummary + if err := json.Unmarshal(body, &rs); err != nil { + t.Fatal(err) + } + if rs.Relabeled != 1 || rs.PosTotal != 1 || rs.NegTotal != 0 { + t.Errorf("run summary should reflect the flip: %+v", rs) + } +} + +// The same flip has to work through rec-feed resolution: the starred row is +// the rec feed's copy, so only the URL ties it to the labeled original. +func TestExportRelabelsRecFeedStar(t *testing.T) { + rec := makeEntries(1, 3000, true, 99) + rec[0].URL = "https://example.com/post/known" + p := &pagedServer{starred: rec} + dir := t.TempDir() + labelsPath := filepath.Join(dir, "labels.jsonl") + + pre := core.LabelRow{ + EntryID: 1234, URL: "https://example.com/post/known", Title: "Known", + FeedID: 7, FeedTitle: "Orig", Label: core.LabelNeg, + CapturedAt: time.Now().Add(-48 * time.Hour).UTC(), + } + if err := core.AppendLabels(labelsPath, []core.LabelRow{pre}); err != nil { + t.Fatal(err) + } + + out := runExportAgainst(t, p, dir, "--rec-feed-id", "99") + if !strings.Contains(out, "relabeled=1") || !strings.Contains(out, "rec-hit=1") { + t.Errorf("expected relabeled=1 rec-hit=1, got:\n%s", out) + } + + labels, err := core.ReadLabels(labelsPath) + if err != nil { + t.Fatal(err) + } + if len(labels) != 1 || labels[0].Label != core.LabelPos { + t.Fatalf("original row should be flipped to pos, got %+v", labels) + } + if labels[0].EntryID != 1234 { + t.Errorf("relabel must keep the original entry id, got %d", labels[0].EntryID) + } +} + func TestExportRecFeedResolution(t *testing.T) { // Two starred entries ON the recommendations feed (feed id 99). rec := makeEntries(2, 3000, true, 99) @@ -237,18 +336,27 @@ func TestExportRecFeedResolution(t *testing.T) { } out := runExportAgainst(t, p, dir, "--rec-feed-id", "99") - if !strings.Contains(out, "rec-hit=1") || !strings.Contains(out, "rec-unresolved=1") { - t.Errorf("expected rec-hit=1 rec-unresolved=1, got:\n%s", out) + if !strings.Contains(out, "rec-hit=1") || !strings.Contains(out, "rec-added=1") { + t.Errorf("expected rec-hit=1 rec-added=1, got:\n%s", out) } - if !strings.Contains(out, "appended=0") { - t.Errorf("rec-feed entries must never append rows, got:\n%s", out) + if !strings.Contains(out, "appended=1") { + t.Errorf("an unresolved rec-feed star should append its own positive, got:\n%s", out) } labels, err := core.ReadLabels(filepath.Join(dir, "labels.jsonl")) if err != nil { t.Fatal(err) } - if len(labels) != 1 { - t.Errorf("labels file should be untouched, has %d rows", len(labels)) + if len(labels) != 2 { + t.Fatalf("expected the seeded row plus one added star, got %d rows", len(labels)) + } + // The resolved star kept the original row (id 1234); the unresolved one + // is a positive carrying the rec copy's id but the original URL. + added := labels[1] + if added.Label != core.LabelPos || added.EntryID != 3001 { + t.Errorf("added row = %+v, want positive for rec entry 3001", added) + } + if added.URL != "https://example.com/post/unknown" { + t.Errorf("added row URL = %q, want the recommendation's article link", added.URL) } } |
