feat: add --prune and a rolled-back --dry-run to bm:seed

This commit is contained in:
Jakub Zych
2026-10-02 17:28:07 +02:00
parent e8411adac3
commit 28f68d002f
6 changed files with 192 additions and 23 deletions

View File

@@ -659,3 +659,64 @@ func TestAPIAgainstDatabase(t *testing.T) {
}
func ptr(s string) *string { return &s }
// TestSeedPrune moves a session, drops a course, renames a course's form and
// drops a reading: --dry-run reports it without writing, --prune applies it.
func TestSeedPrune(t *testing.T) {
_, plugins, gdb := migrated(t)
seedFixture(t, plugins, "testdata/seed.example.json")
next := filepath.Join(t.TempDir(), "next.json")
writeJSON(t, next, `{"meetings": [{"number": 1, "starts_on": "2030-10-05", "ends_on": "2030-10-06", "notes": "przykład"}],
"courses": [{"key": "a-w", "name": "Przedmiot przykładowy A", "form": "wyklad", "semester": 1, "sort_order": 1},
{"key": "b", "name": "Przedmiot przykładowy B", "form": "inne", "semester": 1, "sort_order": 3}],
"class_sessions": [{"course": "a-w", "meeting": 1, "starts_at": "2030-10-05T08:15:00+02:00", "ends_at": "2030-10-05T09:45:00+02:00"},
{"course": "b", "meeting": 1, "starts_at": "2030-10-06T08:00:00+02:00", "ends_at": "2030-10-06T09:30:00+02:00"}],
"readings": [{"course": "a-w", "title": "Lektura przykładowa 1", "is_required": true, "sort_order": 1}]}`)
before := map[string]int64{}
for _, table := range []string{"jz_bm_courses", "jz_bm_class_sessions", "jz_bm_readings", "jz_bm_meetings", "jz_bm_deadlines"} {
before[table] = count(t, gdb, table)
}
var buf bytes.Buffer
if err := bonfire.Call(t.Context(), bmPlugin(t, plugins).Commands(), "bm:seed", []string{next, "--prune", "--dry-run"}, &buf); err != nil {
t.Fatalf("dry run: %v\n%s", err, buf.String())
}
out := buf.String()
for _, want := range []string{"courses\t1\t1\t0\t2", "class_sessions\t2\t0\t0\t3", "readings\t0\t1\t0\t1", "nothing was written", `deleted: course "Przedmiot przykładowy A" (cwiczenia, semester 1) with 1 sessions and 0 readings`} {
if !strings.Contains(out, want) {
t.Errorf("dry run output lacks %q:\n%s", want, out)
}
}
for table, n := range before {
if got := count(t, gdb, table); got != n {
t.Errorf("dry run changed %s: %d -> %d", table, n, got)
}
}
buf.Reset()
if err := bonfire.Call(t.Context(), bmPlugin(t, plugins).Commands(), "bm:seed", []string{next, "--prune"}, &buf); err != nil {
t.Fatalf("prune: %v\n%s", err, buf.String())
}
want := map[string]int64{"jz_bm_courses": 2, "jz_bm_class_sessions": 2, "jz_bm_readings": 1, "jz_bm_meetings": 2, "jz_bm_deadlines": 2}
for table, n := range want {
if got := count(t, gdb, table); got != n {
t.Errorf("after prune %s = %d rows, want %d", table, got, n)
}
}
var orphan int64
gdb.Model(&models.Deadline{}).Where("title = ? AND course_id IS NULL", "Esej przykładowy").Count(&orphan)
if orphan != 1 {
t.Error("the deadline of the pruned course was not unlinked")
}
// Without --prune the same file deletes nothing.
seedFixture(t, plugins, "testdata/seed.example.json")
buf.Reset()
if err := bonfire.Call(t.Context(), bmPlugin(t, plugins).Commands(), "bm:seed", []string{next}, &buf); err != nil {
t.Fatal(err)
}
// The fixture brought back course a-c (its session) and b-k next to b.
if got := count(t, gdb, "jz_bm_courses"); got != 4 || strings.Contains(buf.String(), "deleted:") {
t.Errorf("seed without --prune deleted rows: %d courses\n%s", got, buf.String())
}
}