Skip to content

Commit 174d852

Browse files
fix(serenity): add required type to brand-alias updateProject PATCH (#2721)
<!-- mysticat-pr-skill --> ## 1. Abstract Adds the required `type: 'ai'` field to the serenity brand-alias re-sync `updateProject` PATCH body so the request validates against the Semrush Project Engine gateway. ## 2. Reasoning The brand-alias re-sync in `src/support/serenity/brand-aliases.js` called `transport.updateProject(...)` with a body that omitted the `type` field, which is `required` on `ProjectUpdateRequest`. A live sweep (captured 2026-06-29) confirmed that a typeless `PATCH /v1/workspaces/{id}/projects/{pid}` returns `400` (`Key: 'ProjectUpdateRequest.Type' Error:Field validation for 'Type' failed on the 'required' tag`), while the same PATCH with `type: 'ai'` returns `200`. As a result, any brand-alias change that actually drifts a project's `brand_names` would fail in production and silently no-op the project-level alias sync. The gap went uncaught because no IT exercised the alias-resync PATCH path against the PE mock, even though the mock already 400s a typeless PATCH. ## 3. High-level overview of the changes - Before: the `brand_names` re-sync branch sent `{ brand_name_display?, brand_names }` — no `type` — so a drifted update 400'd against the live gateway and the alias sync silently failed. - After: the PATCH body carries `type: 'ai'` (matching the literal used at project create-time in `handlers/markets.js` and `handlers/markets-subworkspace.js`; AIO projects are always type `ai`), so the request validates and the drifted `brand_names` update succeeds. - Behaviour is otherwise unchanged: the PATCH still fires only when the live `brand_names` set has drifted from the desired set; the benchmark `brand_aliases` PUT path is untouched. - The three exact-body assertions in the unit test now pin `type: 'ai'`, so a future regression that drops the field is caught locally. ## 4. Required information - Jira / issue: #2720 - Other: adobe/spacecat-shared#1746 ## 5. Affected / used mysticat-workspace projects - spacecat-shared (contract): the PE Counterfact mock enforces the `type` requirement at the request-validation seam; spacecat-shared PR #1746 pins the typeless-PATCH 400 as an e2e. This change makes serenity conform to that contract. ## 7. Test plan - (a) Local: ran the `brand-aliases` unit suite, which now asserts `type: 'ai'` on all three `updateProject` body expectations; the serenity `// @ts-check` type-check gate also covers the touched file. No live e2e was run this session — the production defect was identified from the 2026-06-29 live Semrush sweep referenced in the issue. - (b) dev / stage / prod: after deploy, trigger a brand-alias change that drifts a serenity-mode brand's `brand_names` (display name or alias edit) and confirm the `PATCH /v1/workspaces/{id}/projects/{pid}` returns `200` rather than `400`, and that the project's `settings.ai.brand_names` reflects the new set. ## 8. Deployment & merge order - adobe/spacecat-shared#1746 (related): pins the mock-level typeless-PATCH 400 guard upstream. Independent of this PR — no hard merge/deploy ordering required; the two together guard the defect at both the mock layer and the serenity call site. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 0d09469 commit 174d852

2 files changed

Lines changed: 6 additions & 0 deletions

File tree

src/support/serenity/brand-aliases.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ export async function syncBrandAliasesAcrossMarkets(
129129
if (!sameAliasSet(currentBrandNames, desiredBrandNames)) {
130130
// eslint-disable-next-line no-await-in-loop
131131
await transport.updateProject(workspaceId, projectId, {
132+
// `type` is required on ProjectUpdateRequest — the Semrush gateway
133+
// (and the PE mock) 400 a typeless PATCH. AIO projects are always 'ai'.
134+
type: 'ai',
132135
...(hasText(display) ? { brand_name_display: display } : {}),
133136
brand_names: desiredBrandNames,
134137
});

test/support/serenity/brand-aliases.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ describe('brand-aliases helpers', () => {
8787
const result = await syncBrandAliasesAcrossMarkets(transport, aliases, 'Brand', WS, undefined);
8888

8989
expect(transport.updateProject).to.have.been.calledOnceWith(WS, 'p-us', {
90+
type: 'ai',
9091
brand_name_display: 'Brand',
9192
brand_names: ['Brand', 'Acme', 'Acme Inc'],
9293
});
@@ -122,6 +123,7 @@ describe('brand-aliases helpers', () => {
122123
);
123124

124125
expect(transport.updateProject).to.have.been.calledOnceWith(WS, 'p-us', {
126+
type: 'ai',
125127
brand_name_display: 'Brand',
126128
brand_names: ['Brand', 'Acme'],
127129
});
@@ -231,6 +233,7 @@ describe('brand-aliases helpers', () => {
231233
);
232234

233235
expect(transport.updateProject).to.have.been.calledOnceWith(WS, 'p-us', {
236+
type: 'ai',
234237
brand_names: ['Acme'], // no brand_name_display key
235238
});
236239
expect(transport.updateBenchmark).to.not.have.been.called;

0 commit comments

Comments
 (0)