[object Object]

← back to Cli Printing Press

fix(cli): populate parent FK in generated UpsertBatch typed-table test fixture (#1253)

9e0fe96c8488f04a81f1de413a55d7244559679b · 2026-05-12 15:39:07 -0700 · Trevin Chow

buildSubResourceTable emits the parent FK column (e.g. domains_id) as
NOT NULL. The generated TestUpsertBatch_Populates<Name>Table fixture only
supplied an id field, so the typed insert failed with
NOT NULL constraint failed: <table>.<parent>_id on a freshly generated
sub-resource CLI. The test template now detects buildSubResourceTable's
parent FK (first NOT NULL non-PK non-data column) and injects a synthetic
value so the typed insert succeeds.

Closes #1063

Files touched

Diff

commit 9e0fe96c8488f04a81f1de413a55d7244559679b
Author: Trevin Chow <trevin@trevinchow.com>
Date:   Tue May 12 15:39:07 2026 -0700

    fix(cli): populate parent FK in generated UpsertBatch typed-table test fixture (#1253)
    
    buildSubResourceTable emits the parent FK column (e.g. domains_id) as
    NOT NULL. The generated TestUpsertBatch_Populates<Name>Table fixture only
    supplied an id field, so the typed insert failed with
    NOT NULL constraint failed: <table>.<parent>_id on a freshly generated
    sub-resource CLI. The test template now detects buildSubResourceTable's
    parent FK (first NOT NULL non-PK non-data column) and injects a synthetic
    value so the typed insert succeeds.
    
    Closes #1063
---
 internal/generator/generator_test.go               | 59 ++++++++++++++++++++++
 .../templates/store_upsert_batch_test.go.tmpl      | 18 +++++--
 2 files changed, 73 insertions(+), 4 deletions(-)

diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go
index fb290497..038c6537 100644
--- a/internal/generator/generator_test.go
+++ b/internal/generator/generator_test.go
@@ -3076,6 +3076,65 @@ func TestGenerateStoreSubResourceUpsertBindingOrder(t *testing.T) {
 		"swapped (id, data, synced_at, fk) binding order must not be emitted")
 }
 
+// TestGenerateStoreSubResourceUpsertBatchTestSatisfiesNotNullFK pins the
+// emitted TestUpsertBatch_Populates<Name>Table fixture against the NOT NULL
+// parent FK column declared by buildSubResourceTable. Without an injected
+// FK value, the typed insert fails the NOT NULL constraint on a freshly
+// generated CLI.
+func TestGenerateStoreSubResourceUpsertBatchTestSatisfiesNotNullFK(t *testing.T) {
+	t.Parallel()
+
+	apiSpec := &spec.APISpec{
+		Name:    "subres-fk",
+		Version: "0.1.0",
+		BaseURL: "https://api.example.com",
+		Auth:    spec.AuthConfig{Type: "none"},
+		Config: spec.ConfigSpec{
+			Format: "toml",
+			Path:   "~/.config/subres-fk-pp-cli/config.toml",
+		},
+		Resources: map[string]spec.Resource{
+			"domains": {
+				Description: "Manage domains",
+				Endpoints: map[string]spec.Endpoint{
+					"list": {Method: "GET", Path: "/domains", Description: "List domains"},
+				},
+				SubResources: map[string]spec.Resource{
+					"verify": {
+						Description: "Verify a domain",
+						Endpoints: map[string]spec.Endpoint{
+							"get": {
+								Method:      "GET",
+								Path:        "/domains/{domainId}/verify",
+								Description: "Get verification status",
+								Params:      []spec.Param{{Name: "domainId", Type: "string", Required: true, Positional: true}},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	outputDir := filepath.Join(t.TempDir(), naming.CLI(apiSpec.Name))
+	gen := New(apiSpec, outputDir)
+	gen.VisionSet = VisionTemplateSet{Store: true, Sync: true}
+	require.NoError(t, gen.Generate())
+
+	testSrc, err := os.ReadFile(filepath.Join(outputDir, "internal", "store", "upsert_batch_test.go"))
+	require.NoError(t, err)
+	// Anchor all three FK injections to the function body so go test -short
+	// (which skips runGoCommand build/test) still gates a regression that
+	// moved the FK into a comment or only one of the three rows.
+	assert.Regexp(t,
+		`(?s)func TestUpsertBatch_PopulatesVerifyTable\(.*?"domains_id":\s*"[^"]+".*?"domains_id":\s*"[^"]+".*?"domains_id":\s*"[^"]+"`,
+		string(testSrc),
+		"TestUpsertBatch_PopulatesVerifyTable fixture must populate the parent FK column declared NOT NULL by buildSubResourceTable in all three items")
+
+	runGoCommand(t, outputDir, "mod", "tidy")
+	runGoCommand(t, outputDir, "test", "./internal/store")
+}
+
 func TestGenerateSimilarCommandUsesCompositeResourceKey(t *testing.T) {
 	t.Parallel()
 
diff --git a/internal/generator/templates/store_upsert_batch_test.go.tmpl b/internal/generator/templates/store_upsert_batch_test.go.tmpl
index 698a7fd9..919c4c60 100644
--- a/internal/generator/templates/store_upsert_batch_test.go.tmpl
+++ b/internal/generator/templates/store_upsert_batch_test.go.tmpl
@@ -267,8 +267,18 @@ func TestUpsertBatch_ExtractFailuresReturnedForPerItemMisses(t *testing.T) {
 {{- if $hasTyped }}
 {{- range .Tables}}
 {{- if hasTypedTable .}}
+{{- /* $parentFKCol captures buildSubResourceTable's NOT NULL parent FK
+       (e.g. "domains_id"). The fixture must populate it or the typed
+       insert fails the NOT NULL constraint. The predicate is
+       first-match-wins: buildSubResourceTable emits parentCol second
+       (after id, before data/synced_at), so it wins even if a future
+       schema change introduces another NotNull non-PK column. */ -}}
 {{- $hasParentID := false }}
-{{- range .Columns}}{{if eq .Name "parent_id"}}{{$hasParentID = true}}{{end}}{{end}}
+{{- $parentFKCol := "" }}
+{{- range .Columns}}
+{{- if eq .Name "parent_id"}}{{$hasParentID = true}}{{end}}
+{{- if and .NotNull (not .PrimaryKey) (ne .Name "data") (eq $parentFKCol "")}}{{$parentFKCol = .Name}}{{end}}
+{{- end}}
 
 // TestUpsertBatch_Populates{{pascal .Name}}Table verifies that UpsertBatch
 // dispatches paginated items into both the generic resources table AND the
@@ -284,9 +294,9 @@ func TestUpsertBatch_Populates{{pascal .Name}}Table(t *testing.T) {
 	defer s.Close()
 
 	items := []json.RawMessage{
-		json.RawMessage(`{"id": "test-001"}`),
-		json.RawMessage(`{"id": "test-002"}`),
-		json.RawMessage(`{"id": "test-003"}`),
+		json.RawMessage(`{"id": "test-001"{{if $parentFKCol}}, "{{$parentFKCol}}": "test-parent-001"{{end}}}`),
+		json.RawMessage(`{"id": "test-002"{{if $parentFKCol}}, "{{$parentFKCol}}": "test-parent-001"{{end}}}`),
+		json.RawMessage(`{"id": "test-003"{{if $parentFKCol}}, "{{$parentFKCol}}": "test-parent-001"{{end}}}`),
 	}
 	if _, _, err := s.UpsertBatch("{{.Resource}}", items); err != nil {
 		t.Fatalf("UpsertBatch: %v", err)

← bb9b9cb3 fix(cli): make .printing-press.json authoritative for parsed  ·  back to Cli Printing Press  ·  chore(main): release 4.5.2 (#1230) 90798403 →