Files
paliad/internal/services/submission_draft_autoname_live_test.go
mAi f292338919 feat(submissions): auto-name new drafts <date> <client>./.<forum>./.<opponent> (m/paliad#155)
New project-bound submission drafts now default to a sortable, legal-
convention title instead of the bare "Entwurf N" counter:

    <YYYY-MM-DD> <ClientName> ./. <ForumShort> ./. <OpponentName>

- Date leads (ISO, Europe/Berlin) so drafts list chronologically; " ./. "
  is the German legal "gegen" separator.
- Client = root 'client' ancestor of the project tree.
- Forum = proceeding-type jurisdiction (UPC/EPA/DPMA); German proceedings
  resolve to the deciding court (LG/OLG/BGH/BPatG) from the code tail.
- Opponent = primary opposing party, picked by our_side posture
  (active → defendant bucket, reactive → claimant bucket).
- Any segment that resolves empty is omitted with its leading separator;
  a project-less draft keeps the legacy "Entwurf N" scheme entirely.
- Create-time only: existing drafts are never renamed, and a lawyer's
  later manual rename via Update is untouched. Same-slot collisions
  de-duplicate with a " (N)" suffix.

Customization scope (per-user / firm / template, issue #155 Q4) is v1.1 —
the template is hardcoded in submission_autoname.go for now; the override
string is documented as the single extension point on AutoSubmissionTitle.

Example output:
  full:        2026-05-31 Bayer AG ./. UPC ./. Novartis Pharma
  no opponent: 2026-05-31 Bayer AG ./. BPatG
  no forum:    2026-05-31 Bayer AG ./. Novartis Pharma
  date only:   2026-05-31

AutoSubmissionTitle + segment resolvers are pure and table-tested
(submission_autoname_test.go); the Create flow is covered end-to-end
against real Postgres in submission_draft_autoname_live_test.go (gated
on TEST_DATABASE_URL).
2026-05-31 15:28:54 +02:00

130 lines
4.4 KiB
Go

package services
// Live-DB test for the submission-draft auto-naming scheme
// (t-paliad-352 / m/paliad#155). Skipped without TEST_DATABASE_URL.
//
// Verifies the shipped Create flow end-to-end against real Postgres:
// a project-bound draft is auto-named "<date> <client> ./. <forum> ./.
// <opponent>" rather than "Entwurf N", the segments resolve from the
// real project tree (client = root ancestor, forum = proceeding-type
// jurisdiction, opponent = opposing party by our_side), and a second
// draft on the same slot de-duplicates with a " (2)" suffix.
import (
"context"
"os"
"testing"
"time"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
"mgit.msbls.de/m/paliad/internal/db"
)
func TestSubmissionDraft_AutoName_Live(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()
userID := uuid.New()
email := "autoname-" + userID.String()[:8] + "@hlc.com"
var clientID, caseID uuid.UUID
cleanup := func() {
pool.ExecContext(ctx, `DELETE FROM paliad.submission_drafts WHERE user_id = $1`, userID)
pool.ExecContext(ctx, `DELETE FROM paliad.parties WHERE project_id = $1`, caseID)
// Children first (FK), then root.
pool.ExecContext(ctx, `DELETE FROM paliad.project_teams WHERE user_id = $1`, userID)
pool.ExecContext(ctx, `DELETE FROM paliad.projects WHERE id = $1`, caseID)
pool.ExecContext(ctx, `DELETE FROM paliad.projects WHERE id = $1`, clientID)
pool.ExecContext(ctx, `DELETE FROM paliad.users WHERE id = $1`, userID)
pool.ExecContext(ctx, `DELETE FROM auth.users WHERE id = $1`, userID)
}
defer cleanup()
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, 'Auto Name', '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)
// Client root → case child. The case carries the proceeding type
// (UPC) and our_side (claimant), the party is the opponent.
client, err := projects.Create(ctx, userID, CreateProjectInput{
Type: "client", Title: "Bayer AG",
})
if err != nil {
t.Fatalf("create client project: %v", err)
}
clientID = client.ID
ptID := 8 // upc.inf.cfi → jurisdiction UPC
side := "claimant"
caseProj, err := projects.Create(ctx, userID, CreateProjectInput{
Type: "case", Title: "Streitsache", ParentID: &client.ID,
ProceedingTypeID: &ptID, OurSide: &side,
})
if err != nil {
t.Fatalf("create case project: %v", err)
}
caseID = caseProj.ID
beklagte := "Beklagte"
if _, err := parties.Create(ctx, userID, caseProj.ID, CreatePartyInput{
Name: "Novartis Pharma", Role: &beklagte,
}); err != nil {
t.Fatalf("create party: %v", err)
}
loc, _ := time.LoadLocation("Europe/Berlin")
today := time.Now().In(loc).Format("2006-01-02")
wantBase := today + " Bayer AG ./. UPC ./. Novartis Pharma"
d1, err := drafts.Create(ctx, userID, &caseProj.ID, "upc.inf.cfi.sod", "de")
if err != nil {
t.Fatalf("create draft 1: %v", err)
}
if d1.Name != wantBase {
t.Fatalf("draft 1 name = %q, want %q", d1.Name, wantBase)
}
// Second draft on the same (project, code) slot must de-duplicate.
d2, err := drafts.Create(ctx, userID, &caseProj.ID, "upc.inf.cfi.sod", "de")
if err != nil {
t.Fatalf("create draft 2: %v", err)
}
want2 := wantBase + " (2)"
if d2.Name != want2 {
t.Fatalf("draft 2 name = %q, want %q", d2.Name, want2)
}
// A project-less draft keeps the legacy Entwurf-N counter.
dless, err := drafts.Create(ctx, userID, nil, "upc.inf.cfi.sod", "de")
if err != nil {
t.Fatalf("create project-less draft: %v", err)
}
if dless.Name != "Entwurf 1" {
t.Fatalf("project-less draft name = %q, want %q", dless.Name, "Entwurf 1")
}
}