Skip to content

Commit b347c8a

Browse files
committed
fix(vendors): POST upsert replaces metadata on conflict (CodeRabbit)
ON CONFLICT was the one column omitted from the full-overwrite upsert, so a re-POST kept stale metadata — contradicting the documented "full representation" contract. Add `metadata = EXCLUDED.metadata` and assert the clobber (set then reset) in the route spec. https://claude.ai/code/session_015mkdG1VYH3AdqLe4E3i9H6
1 parent e6f5a25 commit b347c8a

2 files changed

Lines changed: 6 additions & 1 deletion

File tree

src/routes/vendors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ vendorRoutes.post('/', async (c) => {
146146
status = EXCLUDED.status,
147147
owner = EXCLUDED.owner,
148148
account_id = EXCLUDED.account_id,
149+
metadata = EXCLUDED.metadata,
149150
risk_score = EXCLUDED.risk_score,
150151
updated_at = NOW()
151152
RETURNING *

tests/routes/vendors.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,20 @@ describe.skipIf(SKIP)('/api/vendors (real Neon)', () => {
102102
category: 'infra',
103103
payment_status: 'failed',
104104
mtd_spend: 200,
105+
metadata: { note: 'first' },
105106
});
106107
expect(first.status).toBe(201);
107108
expect(parseFloat(first.json.mtd_spend)).toBe(200);
109+
expect(first.json.metadata).toEqual({ note: 'first' });
108110

109-
// Re-POST with only name+category — documented behaviour resets the rest.
111+
// Re-POST with only name+category — documented behaviour resets the rest,
112+
// including metadata (ON CONFLICT now assigns metadata = EXCLUDED.metadata).
110113
const second = await api('POST', '/', { vendor_name: name, category: 'data' });
111114
expect(second.status).toBe(201);
112115
expect(second.json.category).toBe('data');
113116
expect(parseFloat(second.json.mtd_spend)).toBe(0); // clobbered to default
114117
expect(second.json.payment_status).toBe('unknown'); // clobbered to default
118+
expect(second.json.metadata).toEqual({}); // clobbered to default (was {note:'first'})
115119
});
116120

117121
it('GET /summary returns spend rollups', async () => {

0 commit comments

Comments
 (0)