From 91a598995c1b2ddad5c9b584a946f54066792c90 Mon Sep 17 00:00:00 2001 From: Selim Date: Fri, 10 Jul 2026 07:35:26 +0300 Subject: [PATCH] =?UTF-8?q?fix(db):=20Supabase=20isim=20drift=20hizalamas?= =?UTF-8?q?=C4=B1=20=E2=80=94=20system=5Fconfigs/linking=5Fcodes=20(PR-SQL?= =?UTF-8?q?-3A)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kodda kullanılan eski tablo adlarını canlı/kanonik adlarla hizalar. YALNIZ kod düzeltmesi — migration/SQL/şema değişikliği YOK, production'a yazma YOK. İş mantığı değişmedi (sadece .from() hedefi + kolon paritesi doğrulandı). Parite doğrulandı → hizalandı: - system_configs → runtime_policies (superAdminService getSystemPolicies/updatePolicy); kolonlar id/key/name/value/unit/updated_at canlı runtime_policies'te mevcut. - linking_codes → vehicle_linking_codes (website vehicle/code API); insert {vehicle_id, code, expires_at} canlı şemayla birebir uyumlu. Parite YOK → KAPSAM DIŞI (raporlandı): - push_subscriptions → vehicle_push_tokens: kod endpoint/subscription/user_id + onConflict:'endpoint' kullanıyor; canlı tablo fcm_token/platform/vehicle_id (FCM≠Web Push). Kör rename akışı kırardı → DOKUNULMADI. - src/__tests__/supabaseNameDrift.test.ts: .from() hedeflerini kilitler (mock) + eski adların aktif kod yolunda kalmadığını ?raw kaynak-kilidiyle doğrular. Dokunulmadı: remote_commands · memberships/tenant · radar_reports/raw_community_events · sentry_clips (bucket). tsc -b temiz · eslint temiz · 2493 test yeşil (23 bilinen env-gated) · production build yeşil. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Fauew13VP3vbfKqnocPdfX --- src/__tests__/supabaseNameDrift.test.ts | 70 ++++++++++++++++++++ src/components/admin/SuperAdminShell.tsx | 2 +- src/platform/superadmin/superAdminService.ts | 8 ++- website/src/app/api/vehicle/code/route.ts | 2 +- 4 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 src/__tests__/supabaseNameDrift.test.ts diff --git a/src/__tests__/supabaseNameDrift.test.ts b/src/__tests__/supabaseNameDrift.test.ts new file mode 100644 index 00000000..64cf88ec --- /dev/null +++ b/src/__tests__/supabaseNameDrift.test.ts @@ -0,0 +1,70 @@ +/** + * supabaseNameDrift.test.ts — PR-SQL-3A: Supabase isim drift hizalaması kilidi. + * + * Eski tablo adları canlı/kanonik adlarla eşitlendi: + * system_configs → runtime_policies (superAdminService) + * linking_codes → vehicle_linking_codes (website vehicle/code API) + * (push_subscriptions → vehicle_push_tokens KAPSAM DIŞI: kolon paritesi yok — Web Push ≠ FCM.) + * + * Bu test: (1) davranışsal — superAdminService .from() hedeflerini açıkça kilitler; + * (2) kaynak-kilit — eski adların ilgili aktif kod yollarında kalmadığını doğrular (?raw). + */ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +// Aktif kod yolu kaynakları (transform-time sabit — paralel flake'e bağışık). +import superAdminSrc from '../platform/superadmin/superAdminService.ts?raw'; +import vehicleCodeRouteSrc from '../../website/src/app/api/vehicle/code/route.ts?raw'; + +/* ── Supabase admin client mock — .from() hedeflerini yakalar ─────────────── */ +const fromCalls: string[] = []; +vi.mock('../platform/roleSystem/RoleStore', () => ({ + getAdminClient: () => { + const q: Record = {}; + Object.assign(q, { + select: () => q, + order: () => Promise.resolve({ data: [{ id: 'p1', key: 'max_speed', name: 'Max Hız', value: 120, unit: 'km/h', updated_at: '2026-01-01T00:00:00Z' }], error: null }), + update: () => q, + insert: () => Promise.resolve({ data: null, error: null }), + eq: () => Promise.resolve({ error: null }), + }); + return { + auth: { getUser: async () => ({ data: { user: { id: 'u1', email: 'admin@x.co' } } }) }, + from: (t: string) => { fromCalls.push(t); return q; }, + }; + }, +})); + +import { getSystemPolicies, updatePolicy } from '../platform/superadmin/superAdminService'; + +beforeEach(() => { fromCalls.length = 0; }); + +/* ── Davranışsal: .from() hedefi kilidi ──────────────────────────────────── */ +describe('superAdminService — runtime_policies hizalaması', () => { + it('getSystemPolicies runtime_policies üzerinden OKUR (system_configs DEĞİL)', async () => { + const res = await getSystemPolicies(); + expect(fromCalls).toContain('runtime_policies'); + expect(fromCalls).not.toContain('system_configs'); + expect(res[0]?.key).toBe('max_speed'); + }); + + it('updatePolicy runtime_policies üzerinden YAZAR (system_configs DEĞİL) + audit', async () => { + await updatePolicy('max_speed', 130); + expect(fromCalls).toContain('runtime_policies'); + expect(fromCalls).not.toContain('system_configs'); + expect(fromCalls).toContain('audit_logs'); // logAdminAction akışı korundu + }); +}); + +/* ── Kaynak-kilit: eski adlar aktif kod yollarında kalmadı ───────────────── */ +describe('isim drift — eski adlar aktif kod yolunda YOK', () => { + it('superAdminService: from(\'system_configs\') kalmadı; runtime_policies var', () => { + expect(superAdminSrc).not.toMatch(/from\(\s*['"]system_configs['"]/); + expect(superAdminSrc).toMatch(/from\(\s*['"]runtime_policies['"]/); + }); + + it('website vehicle/code API: from(\'linking_codes\') kalmadı; vehicle_linking_codes var', () => { + // 'vehicle_linking_codes' eşleşmesin diye tırnak-hemen-sonrası guard. + expect(vehicleCodeRouteSrc).not.toMatch(/from\(\s*['"]linking_codes['"]/); + expect(vehicleCodeRouteSrc).toMatch(/from\(\s*['"]vehicle_linking_codes['"]/); + }); +}); diff --git a/src/components/admin/SuperAdminShell.tsx b/src/components/admin/SuperAdminShell.tsx index 26aa7ba4..d2f0f9b7 100644 --- a/src/components/admin/SuperAdminShell.tsx +++ b/src/components/admin/SuperAdminShell.tsx @@ -514,7 +514,7 @@ export function SuperAdminShell() { ) : policiesList.length === 0 ? (

- POLİTİKA_YOK: system_configs tablosu boş + POLİTİKA_YOK: runtime_policies tablosu boş

) : ( policiesList.map((policy) => ( diff --git a/src/platform/superadmin/superAdminService.ts b/src/platform/superadmin/superAdminService.ts index e3f7daa9..0a321707 100644 --- a/src/platform/superadmin/superAdminService.ts +++ b/src/platform/superadmin/superAdminService.ts @@ -587,7 +587,9 @@ export async function updateRolloutStatus( } /** - * system_configs tablosundaki aktif politikaları döner. + * runtime_policies tablosundaki aktif politikaları döner. + * (İsim hizalama: eski `system_configs` adı canlı/kanonik `runtime_policies` ile eşitlendi; + * kolon paritesi doğrulandı — id/key/name/value/unit/updated_at mevcut.) */ export async function getSystemPolicies(): Promise { const client = getAdminClient(); @@ -595,7 +597,7 @@ export async function getSystemPolicies(): Promise { try { const { data, error } = await client - .from('system_configs') + .from('runtime_policies') .select('id, key, name, value, unit, updated_at') .order('key', { ascending: true }); @@ -638,7 +640,7 @@ export async function updatePolicy( }); // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { error } = await (client.from('system_configs') as any) + const { error } = await (client.from('runtime_policies') as any) .update({ value: String(value), updated_at: new Date().toISOString() }) .eq('key', key); diff --git a/website/src/app/api/vehicle/code/route.ts b/website/src/app/api/vehicle/code/route.ts index 903df570..720656ed 100644 --- a/website/src/app/api/vehicle/code/route.ts +++ b/website/src/app/api/vehicle/code/route.ts @@ -49,7 +49,7 @@ export async function POST(req: NextRequest) { const expiresAt = new Date(Date.now() + 60_000).toISOString(); const { error } = await supabaseAdmin - .from('linking_codes') + .from('vehicle_linking_codes') .insert({ vehicle_id: vehicle.id, code, expires_at: expiresAt }); if (error) {