aboutsummaryrefslogtreecommitdiff
path: root/cmds/export_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmds/export_test.go')
-rw-r--r--cmds/export_test.go120
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)
}
}