diff --git a/src/lib/pushers/model-pusher.ts b/src/lib/pushers/model-pusher.ts index 6a9f689..b15857b 100644 --- a/src/lib/pushers/model-pusher.ts +++ b/src/lib/pushers/model-pusher.ts @@ -20,10 +20,19 @@ function modelTypeMatches(a: mgmtApi.Model, b: mgmtApi.Model): boolean { return aType === bType; } -/** Human-readable model kind for messages. 1 = content, 2 = component/module. */ +/** + * Human-readable model kind for messages. + * + * PROD-2315: per the SDK's ContentDefinitionTypeID enum, 0 = List (a content model backing a + * container/content list — the common case) and 1 = SingleItem (a content model for a single + * item) are BOTH content models; only 2 = Component (module/component) is the other kind. This + * previously only recognized 1 as "content" and let 0 fall through to the internal `"type 0"` + * label — which fired on most real collisions, since list-backed content models (0) are far more + * common than single-item ones (1). + */ function modelKindName(model: mgmtApi.Model): string { const t = (model as any)?.contentDefinitionTypeID; - return t === 1 ? "content" : t === 2 ? "component/module" : `type ${t}`; + return t === 0 || t === 1 ? "content" : t === 2 ? "component/module" : `type ${t}`; } /** diff --git a/src/lib/pushers/tests/model-pusher.test.ts b/src/lib/pushers/tests/model-pusher.test.ts index 94a22ad..e50f5fa 100644 --- a/src/lib/pushers/tests/model-pusher.test.ts +++ b/src/lib/pushers/tests/model-pusher.test.ts @@ -592,6 +592,24 @@ describe("pushModels — cross-kind reference-name collision (PROD-2315)", () => expect(result.failureDetails![0].error).toMatch(/content model with that reference name already exists/); }); + it("labels a List-type (contentDefinitionTypeID 0) collision as \"content\", not \"type 0\" (PROD-2315 wording)", async () => { + const saveModel = jest.fn(); + jest.spyOn(stateModule, "getApiClient").mockReturnValue(makeApiClient(saveModel)); + + const { pushModels } = await import("../model-pusher"); + + // Source LinkCard is a List-backed content model (type 0) — the common case for real + // container-backed models — colliding with a component (type 2) on the target. + const sourceModel = makeModel({ referenceName: "LinkCard", contentDefinitionTypeID: 0 }); + const targetModel = makeModel({ id: 504, referenceName: "LinkCard", contentDefinitionTypeID: 2 }); + + const result = await pushModels([sourceModel], [targetModel]); + + expect(result.failureDetails).toHaveLength(1); + expect(result.failureDetails![0].error).toMatch(/is a content model on the source/); + expect(result.failureDetails![0].error).not.toMatch(/type 0/); + }); + it("does NOT flag a same-name model when the kinds are unknown (falls back to adopt-by-reference)", async () => { const saveModel = jest.fn(); jest.spyOn(stateModule, "getApiClient").mockReturnValue(makeApiClient(saveModel));