package services // Live-DB tests for Slice F section service additions (Create + Delete // + Reorder). Gated on TEST_DATABASE_URL, mirroring Slice A's pattern. import ( "context" "os" "testing" "github.com/google/uuid" "github.com/jmoiron/sqlx" _ "github.com/lib/pq" "mgit.msbls.de/m/paliad/internal/db" ) func TestSectionService_SliceF(t *testing.T) { url := os.Getenv("TEST_DATABASE_URL") if url == "" { t.Skip("TEST_DATABASE_URL not set — skipping live DB test") } if err := db.ApplyMigrations(url); err != nil { t.Fatalf("apply migrations: %v", err) } pool, err := sqlx.Connect("postgres", url) if err != nil { t.Fatalf("connect: %v", err) } defer pool.Close() ctx := context.Background() bases := NewBaseService(pool) sections := NewSectionService(pool) // Seed user + draft so we have a draft_id to attach sections to. userID := uuid.New() cleanup := func() { pool.ExecContext(ctx, `DELETE FROM paliad.submission_sections WHERE draft_id IN (SELECT id FROM paliad.submission_drafts WHERE user_id = $1)`, userID) pool.ExecContext(ctx, `DELETE FROM paliad.submission_drafts WHERE user_id = $1`, userID) pool.ExecContext(ctx, `DELETE FROM paliad.users WHERE id = $1`, userID) pool.ExecContext(ctx, `DELETE FROM auth.users WHERE id = $1`, userID) } cleanup() defer cleanup() email := "slice-f-" + userID.String()[:8] + "@hlc.com" if _, err := pool.ExecContext(ctx, `INSERT INTO auth.users (id, email) VALUES ($1, $2)`, userID, email); err != nil { t.Fatalf("seed auth.users: %v", err) } if _, err := pool.ExecContext(ctx, `INSERT INTO paliad.users (id, email, display_name, office, global_role, lang) VALUES ($1, $2, 'Slice F User', 'munich', 'standard', 'de')`, userID, email); err != nil { t.Fatalf("seed paliad.users: %v", err) } users := NewUserService(pool) projects := NewProjectService(pool, users) parties := NewPartyService(pool, projects) vars := NewSubmissionVarsService(pool, projects, parties, users) renderer := NewSubmissionRenderer() drafts := NewSubmissionDraftService(pool, projects, vars, renderer) drafts.AttachComposer(bases, sections, "HLC") d, err := drafts.Create(ctx, userID, nil, "de.inf.lg.erwidg", "de") if err != nil { t.Fatalf("Create draft: %v", err) } initial, err := sections.ListForDraft(ctx, d.ID) if err != nil { t.Fatalf("ListForDraft initial: %v", err) } if len(initial) != 10 { t.Fatalf("expected 10 seeded sections; got %d", len(initial)) } t.Run("Create custom section", func(t *testing.T) { created, err := sections.Create(ctx, SectionCreateInput{ DraftID: d.ID, SectionKey: "berufungsantraege", Kind: "requests", LabelDE: "Berufungsanträge", LabelEN: "Appeal requests", Included: true, }) if err != nil { t.Fatalf("Create: %v", err) } if created.OrderIndex <= 10 { t.Errorf("auto-assigned order_index should be > existing max; got %d", created.OrderIndex) } // Slug collision must surface as ErrInvalidInput. _, err = sections.Create(ctx, SectionCreateInput{ DraftID: d.ID, SectionKey: "berufungsantraege", Kind: "prose", LabelDE: "x", LabelEN: "x", Included: true, }) if err == nil { t.Errorf("expected unique-key collision error; got nil") } }) t.Run("Delete section", func(t *testing.T) { // Grab one of the seeded rows to delete. current, _ := sections.ListForDraft(ctx, d.ID) var victimID uuid.UUID for _, s := range current { if s.SectionKey == "exhibits" { victimID = s.ID break } } if victimID == uuid.Nil { t.Fatalf("expected exhibits section to exist") } if err := sections.Delete(ctx, victimID); err != nil { t.Fatalf("Delete: %v", err) } // Second delete returns not-found. if err := sections.Delete(ctx, victimID); err == nil { t.Errorf("expected ErrSubmissionSectionNotFound on second delete") } }) t.Run("Reorder sections", func(t *testing.T) { current, _ := sections.ListForDraft(ctx, d.ID) if len(current) < 3 { t.Skipf("need at least 3 sections to test reorder; got %d", len(current)) } // Reverse the order list. ids := make([]uuid.UUID, 0, len(current)) for i := len(current) - 1; i >= 0; i-- { ids = append(ids, current[i].ID) } reordered, err := sections.Reorder(ctx, d.ID, ids) if err != nil { t.Fatalf("Reorder: %v", err) } // Verify the first ID in our list now has the lowest order_index. if reordered[0].ID != ids[0] { t.Errorf("first ID after reorder = %s; want %s", reordered[0].ID, ids[0]) } // Order indices should be ascending. prev := 0 for _, s := range reordered { if s.OrderIndex <= prev { t.Errorf("non-ascending order_index after reorder: %d (prev=%d) at %s", s.OrderIndex, prev, s.SectionKey) } prev = s.OrderIndex } }) }