2026-07-16 07:26:04 +02:00
|
|
|
-- =====================================================================
|
|
|
|
|
-- TeeCup — isolasjonstest (oppførsel, ikke bare skjema)
|
|
|
|
|
-- =====================================================================
|
|
|
|
|
-- Beviser at RLS faktisk hindrer én organisasjon i å se en annens data,
|
|
|
|
|
-- sett fra runtime-rollen teecup_app (som IKKE er superuser/BYPASSRLS).
|
|
|
|
|
--
|
|
|
|
|
-- Kjør som admin/superbruker mot en scratch- eller test-database der
|
RLS-tomstreng-buggen er fikset og verifisert grundig.
Fiksen: ny migrasjon 005_rls_null_guard.sql — én delt STABLE SQL-funksjon app_current_org() (NULLIF(current_setting('app.current_org', true), '')::uuid) erstatter det rå uttrykket i alle 15 RLS-policyer (14 org_isolation + org_self) via ALTER POLICY. NULLIF konverterer tomstreng til NULL før cast, så "trygg standard: se ingenting" gjenopprettes uansett GUC-tilstand.
Verifisert to ganger, ulikt:
Tre nye regresjonstester i test_isolation.sql (Test 10–12): tomstreng-lesing gir 0 rader ikke krasj, tomstreng-skriving avvises av RLS ikke krasj, og org-bootstrap-innsetting (tidligere aldri testet) fungerer rett etter tomstreng-tilstand.
Faktisk gjenskaping av original-buggen mot en ekte container (pool-størrelse tvunget til 1 for å garantere tilkoblings-gjenbruk): varmet opp med et vanlig org_connection()-kall, kalte deretter /auth/me på samme tilkobling — gikk fra 500 til 200.
En viktig ting jeg tok feil om i forrige runde, oppdaget ved å faktisk teste i stedet for å anta: fiksen gjør ikke at /auth/me trygt kan joine organization direkte via plain_connection(). Den løser tomstreng-krasjen, men org_self-policyen krever fortsatt en matchende app.current_org for å vise noen rad i det hele tatt — riktig RLS-design, ikke noe fiksen skulle endre. Siden en bruker kan tilhøre flere organisasjoner, finnes det ingen én kontekst å sette. Rettet til riktig løsning: /auth/me slår nå opp hvert org-navn enkeltvis via org_connection() (verifisert at det faktisk returnerer navnet korrekt).
Alt dokumentert i CLAUDE.md/FEATURE_BACKLOG.md.
2026-07-16 15:34:24 +02:00
|
|
|
-- 001_initial_schema.sql, 002_roles_and_grants.sql,
|
|
|
|
|
-- 003_scoring_and_blinddraw.sql og 005_rls_null_guard.sql allerede er kjørt
|
|
|
|
|
-- (003 kreves for match_hole_result/lineup_lock, 005 for
|
|
|
|
|
-- app_current_org()-funksjonen testene 10-12 bruker indirekte):
|
2026-07-16 07:26:04 +02:00
|
|
|
-- psql -d teecup_scratch -f test_isolation.sql
|
|
|
|
|
--
|
|
|
|
|
-- Alt kjøres i én transaksjon som RULLES TILBAKE til slutt — ingen testdata
|
|
|
|
|
-- blir liggende igjen. Skriptet stopper med feil hvis isolasjonen svikter.
|
|
|
|
|
-- =====================================================================
|
|
|
|
|
|
|
|
|
|
\set ON_ERROR_STOP on
|
|
|
|
|
|
|
|
|
|
BEGIN;
|
|
|
|
|
|
|
|
|
|
-- --- Seed to organisasjoner + én turnering hver (som privilegert rolle) ---
|
|
|
|
|
-- (Superbruker omgår RLS, så innsettingen går uhindret her — det er meningen.)
|
|
|
|
|
INSERT INTO organization (id, name) VALUES
|
|
|
|
|
('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'Org A'),
|
|
|
|
|
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'Org B');
|
|
|
|
|
|
ackend for the join-code, leading_side, and projected-standings work is fully verified against a fresh scratch database (migrations 001–011, test_isolation.sql 12/12, and live end-to-end checks: join-code generation/uniqueness, code resolution, visibility bypass on both the tournament view and registration — including case-insensitivity and rejection of a wrong code — plus a full hole-by-hole match simulation confirming leading_side/status_text stay in sync through "1 UP" → "AS" → a decided "9&7", with the leaderboard's projected points matching at each stage: 1.0/0.0 while A led, 0.5/0.5 at all-square, and settling to equal actual/projected once decided).
Ready to deploy to the real system:
Migration: 011_join_code_and_leading_side.sql against real teecup_db (adds tournament.join_code — backfills existing tournaments with generated codes — and match.leading_side, plus the public_tournament_by_code() function).
Redeploy: teecup_api only (no frontend changes yet — those come next).
2026-07-19 09:23:35 +02:00
|
|
|
INSERT INTO tournament (organization_id, name, join_code) VALUES
|
|
|
|
|
('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'A-Cup', 'TESTA1'),
|
|
|
|
|
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'B-Cup', 'TESTB1');
|
2026-07-16 07:26:04 +02:00
|
|
|
|
2026-07-16 08:21:57 +02:00
|
|
|
-- --- Seed nok struktur til å teste match_hole_result og lineup_lock ---
|
|
|
|
|
-- (ADR-012/013, migrasjon 003). Fortsatt privilegert rolle, RLS omgås her.
|
|
|
|
|
INSERT INTO course (id, organization_id, name) VALUES
|
|
|
|
|
('c1111111-1111-1111-1111-111111111111', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'A-banen'),
|
|
|
|
|
('c2222222-2222-2222-2222-222222222222', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'B-banen');
|
|
|
|
|
|
|
|
|
|
INSERT INTO team (id, organization_id, tournament_id, name)
|
|
|
|
|
SELECT 'de111111-1111-1111-1111-111111111111', t.organization_id, t.id, 'A rødt'
|
|
|
|
|
FROM tournament t WHERE t.organization_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
|
|
|
|
INSERT INTO team (id, organization_id, tournament_id, name)
|
|
|
|
|
SELECT 'de222222-2222-2222-2222-222222222222', t.organization_id, t.id, 'A blått'
|
|
|
|
|
FROM tournament t WHERE t.organization_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
|
|
|
|
INSERT INTO team (id, organization_id, tournament_id, name)
|
|
|
|
|
SELECT 'df111111-1111-1111-1111-111111111111', t.organization_id, t.id, 'B rødt'
|
|
|
|
|
FROM tournament t WHERE t.organization_id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb';
|
|
|
|
|
INSERT INTO team (id, organization_id, tournament_id, name)
|
|
|
|
|
SELECT 'df222222-2222-2222-2222-222222222222', t.organization_id, t.id, 'B blått'
|
|
|
|
|
FROM tournament t WHERE t.organization_id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb';
|
|
|
|
|
|
|
|
|
|
INSERT INTO session (id, organization_id, tournament_id, sequence, format, course_id)
|
|
|
|
|
SELECT 'e5111111-1111-1111-1111-111111111111', t.organization_id, t.id, 1, 'singles',
|
|
|
|
|
'c1111111-1111-1111-1111-111111111111'
|
|
|
|
|
FROM tournament t WHERE t.organization_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
|
|
|
|
INSERT INTO session (id, organization_id, tournament_id, sequence, format, course_id)
|
|
|
|
|
SELECT 'e5222222-2222-2222-2222-222222222222', t.organization_id, t.id, 1, 'singles',
|
|
|
|
|
'c2222222-2222-2222-2222-222222222222'
|
|
|
|
|
FROM tournament t WHERE t.organization_id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb';
|
|
|
|
|
|
|
|
|
|
INSERT INTO match (id, organization_id, session_id, sequence, team_a_id, team_b_id) VALUES
|
|
|
|
|
('fa111111-1111-1111-1111-111111111111', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
|
|
|
|
'e5111111-1111-1111-1111-111111111111', 1,
|
|
|
|
|
'de111111-1111-1111-1111-111111111111', 'de222222-2222-2222-2222-222222222222'),
|
|
|
|
|
('fa222222-2222-2222-2222-222222222222', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb',
|
|
|
|
|
'e5222222-2222-2222-2222-222222222222', 1,
|
|
|
|
|
'df111111-1111-1111-1111-111111111111', 'df222222-2222-2222-2222-222222222222');
|
|
|
|
|
|
|
|
|
|
INSERT INTO match_hole_result (organization_id, match_id, hole_number, winning_side) VALUES
|
|
|
|
|
('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'fa111111-1111-1111-1111-111111111111', 1, 'a'),
|
|
|
|
|
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'fa222222-2222-2222-2222-222222222222', 1, 'b');
|
|
|
|
|
|
|
|
|
|
INSERT INTO lineup_lock (organization_id, session_id, team_id) VALUES
|
|
|
|
|
('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'e5111111-1111-1111-1111-111111111111',
|
|
|
|
|
'de111111-1111-1111-1111-111111111111'),
|
|
|
|
|
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'e5222222-2222-2222-2222-222222222222',
|
|
|
|
|
'df111111-1111-1111-1111-111111111111');
|
|
|
|
|
|
2026-07-16 07:26:04 +02:00
|
|
|
-- --- Bytt til runtime-rollen: nå SKAL RLS gjelde ---------------------
|
|
|
|
|
SET ROLE teecup_app;
|
|
|
|
|
SELECT set_config('app.current_org', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', false);
|
|
|
|
|
|
|
|
|
|
-- Test 1: Ser bare Org A sine turneringer (1, ikke 2).
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE n int;
|
|
|
|
|
BEGIN
|
|
|
|
|
SELECT count(*) INTO n FROM tournament;
|
|
|
|
|
IF n <> 1 THEN
|
|
|
|
|
RAISE EXCEPTION 'ISOLASJON FEILET (lesing): ser % turneringer, forventet 1', n;
|
|
|
|
|
END IF;
|
|
|
|
|
RAISE NOTICE 'OK Test 1: ser kun egen organisasjons turneringer (%).', n;
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
-- Test 2: Ser bare sin egen organisasjonsrad.
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE n int;
|
|
|
|
|
BEGIN
|
|
|
|
|
SELECT count(*) INTO n FROM organization;
|
|
|
|
|
IF n <> 1 THEN
|
|
|
|
|
RAISE EXCEPTION 'ISOLASJON FEILET (organization): ser % rader, forventet 1', n;
|
|
|
|
|
END IF;
|
|
|
|
|
RAISE NOTICE 'OK Test 2: ser kun egen organisasjonsrad (%).', n;
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
-- Test 3: Kan IKKE sette inn data merket med en annen organisasjon.
|
|
|
|
|
-- WITH CHECK skal blokkere det (SQLSTATE 42501).
|
|
|
|
|
DO $$
|
|
|
|
|
BEGIN
|
|
|
|
|
BEGIN
|
ackend for the join-code, leading_side, and projected-standings work is fully verified against a fresh scratch database (migrations 001–011, test_isolation.sql 12/12, and live end-to-end checks: join-code generation/uniqueness, code resolution, visibility bypass on both the tournament view and registration — including case-insensitivity and rejection of a wrong code — plus a full hole-by-hole match simulation confirming leading_side/status_text stay in sync through "1 UP" → "AS" → a decided "9&7", with the leaderboard's projected points matching at each stage: 1.0/0.0 while A led, 0.5/0.5 at all-square, and settling to equal actual/projected once decided).
Ready to deploy to the real system:
Migration: 011_join_code_and_leading_side.sql against real teecup_db (adds tournament.join_code — backfills existing tournaments with generated codes — and match.leading_side, plus the public_tournament_by_code() function).
Redeploy: teecup_api only (no frontend changes yet — those come next).
2026-07-19 09:23:35 +02:00
|
|
|
INSERT INTO tournament (organization_id, name, join_code)
|
|
|
|
|
VALUES ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'kryss-org-forsøk', 'TESTX01');
|
2026-07-16 07:26:04 +02:00
|
|
|
-- Kommer vi hit, ble det tillatt -> isolasjonen er brutt.
|
|
|
|
|
RAISE EXCEPTION 'ISOLASJON FEILET (skriving): fikk sette inn data for annen org';
|
|
|
|
|
EXCEPTION
|
|
|
|
|
WHEN insufficient_privilege THEN
|
|
|
|
|
RAISE NOTICE 'OK Test 3: WITH CHECK blokkerte kryss-org innsetting.';
|
|
|
|
|
END;
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
-- Test 4: Kontrollprøve — bytter kontekst til Org B og ser Bs turnering isteden.
|
|
|
|
|
SELECT set_config('app.current_org', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', false);
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE nm text;
|
|
|
|
|
BEGIN
|
|
|
|
|
SELECT name INTO nm FROM tournament;
|
|
|
|
|
IF nm <> 'B-Cup' THEN
|
|
|
|
|
RAISE EXCEPTION 'ISOLASJON FEILET (kontekstbytte): forventet B-Cup, fikk %', nm;
|
|
|
|
|
END IF;
|
|
|
|
|
RAISE NOTICE 'OK Test 4: kontekstbytte gir riktig organisasjons data (%).', nm;
|
|
|
|
|
END $$;
|
|
|
|
|
|
2026-07-16 08:21:57 +02:00
|
|
|
-- --- Tilbake til Org A-kontekst for de nye tabellene (ADR-012/013) ----
|
|
|
|
|
SELECT set_config('app.current_org', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', false);
|
|
|
|
|
|
|
|
|
|
-- Test 5: match_hole_result — ser bare Org A sin rad (1, ikke 2).
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE n int;
|
|
|
|
|
BEGIN
|
|
|
|
|
SELECT count(*) INTO n FROM match_hole_result;
|
|
|
|
|
IF n <> 1 THEN
|
|
|
|
|
RAISE EXCEPTION 'ISOLASJON FEILET (match_hole_result lesing): ser % rader, forventet 1', n;
|
|
|
|
|
END IF;
|
|
|
|
|
RAISE NOTICE 'OK Test 5: match_hole_result — ser kun egen organisasjons rad (%).', n;
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
-- Test 6: match_hole_result — kan IKKE sette inn en rad merket med annen org,
|
|
|
|
|
-- selv om match_id-en (fra org B) i seg selv er FK-gyldig.
|
|
|
|
|
DO $$
|
|
|
|
|
BEGIN
|
|
|
|
|
BEGIN
|
|
|
|
|
INSERT INTO match_hole_result (organization_id, match_id, hole_number, winning_side)
|
|
|
|
|
VALUES ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb',
|
|
|
|
|
'fa222222-2222-2222-2222-222222222222', 2, 'b');
|
|
|
|
|
RAISE EXCEPTION 'ISOLASJON FEILET (match_hole_result skriving): fikk sette inn data for annen org';
|
|
|
|
|
EXCEPTION
|
|
|
|
|
WHEN insufficient_privilege THEN
|
|
|
|
|
RAISE NOTICE 'OK Test 6: match_hole_result — WITH CHECK blokkerte kryss-org innsetting.';
|
|
|
|
|
END;
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
-- Test 7: lineup_lock — ser bare Org A sin rad (1, ikke 2).
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE n int;
|
|
|
|
|
BEGIN
|
|
|
|
|
SELECT count(*) INTO n FROM lineup_lock;
|
|
|
|
|
IF n <> 1 THEN
|
|
|
|
|
RAISE EXCEPTION 'ISOLASJON FEILET (lineup_lock lesing): ser % rader, forventet 1', n;
|
|
|
|
|
END IF;
|
|
|
|
|
RAISE NOTICE 'OK Test 7: lineup_lock — ser kun egen organisasjons rad (%).', n;
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
-- Test 8: lineup_lock — kan IKKE sette inn en rad merket med annen org,
|
|
|
|
|
-- selv om session_id/team_id (fra org B) i seg selv er FK-gyldige.
|
|
|
|
|
DO $$
|
|
|
|
|
BEGIN
|
|
|
|
|
BEGIN
|
|
|
|
|
INSERT INTO lineup_lock (organization_id, session_id, team_id)
|
|
|
|
|
VALUES ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb',
|
|
|
|
|
'e5222222-2222-2222-2222-222222222222',
|
|
|
|
|
'df222222-2222-2222-2222-222222222222');
|
|
|
|
|
RAISE EXCEPTION 'ISOLASJON FEILET (lineup_lock skriving): fikk sette inn data for annen org';
|
|
|
|
|
EXCEPTION
|
|
|
|
|
WHEN insufficient_privilege THEN
|
|
|
|
|
RAISE NOTICE 'OK Test 8: lineup_lock — WITH CHECK blokkerte kryss-org innsetting.';
|
|
|
|
|
END;
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
-- Test 9: kontekstbytte til Org B gir Org Bs rader for begge nye tabeller.
|
|
|
|
|
SELECT set_config('app.current_org', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', false);
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE ws team_side;
|
|
|
|
|
DECLARE tid uuid;
|
|
|
|
|
BEGIN
|
|
|
|
|
SELECT winning_side INTO ws FROM match_hole_result;
|
|
|
|
|
IF ws <> 'b' THEN
|
|
|
|
|
RAISE EXCEPTION 'ISOLASJON FEILET (match_hole_result kontekstbytte): forventet b, fikk %', ws;
|
|
|
|
|
END IF;
|
|
|
|
|
RAISE NOTICE 'OK Test 9a: match_hole_result — kontekstbytte gir riktig organisasjons data (%).', ws;
|
|
|
|
|
|
|
|
|
|
SELECT team_id INTO tid FROM lineup_lock;
|
|
|
|
|
IF tid <> 'df111111-1111-1111-1111-111111111111' THEN
|
|
|
|
|
RAISE EXCEPTION 'ISOLASJON FEILET (lineup_lock kontekstbytte): forventet Org Bs lag, fikk %', tid;
|
|
|
|
|
END IF;
|
|
|
|
|
RAISE NOTICE 'OK Test 9b: lineup_lock — kontekstbytte gir riktig organisasjons data (%).', tid;
|
|
|
|
|
END $$;
|
|
|
|
|
|
RLS-tomstreng-buggen er fikset og verifisert grundig.
Fiksen: ny migrasjon 005_rls_null_guard.sql — én delt STABLE SQL-funksjon app_current_org() (NULLIF(current_setting('app.current_org', true), '')::uuid) erstatter det rå uttrykket i alle 15 RLS-policyer (14 org_isolation + org_self) via ALTER POLICY. NULLIF konverterer tomstreng til NULL før cast, så "trygg standard: se ingenting" gjenopprettes uansett GUC-tilstand.
Verifisert to ganger, ulikt:
Tre nye regresjonstester i test_isolation.sql (Test 10–12): tomstreng-lesing gir 0 rader ikke krasj, tomstreng-skriving avvises av RLS ikke krasj, og org-bootstrap-innsetting (tidligere aldri testet) fungerer rett etter tomstreng-tilstand.
Faktisk gjenskaping av original-buggen mot en ekte container (pool-størrelse tvunget til 1 for å garantere tilkoblings-gjenbruk): varmet opp med et vanlig org_connection()-kall, kalte deretter /auth/me på samme tilkobling — gikk fra 500 til 200.
En viktig ting jeg tok feil om i forrige runde, oppdaget ved å faktisk teste i stedet for å anta: fiksen gjør ikke at /auth/me trygt kan joine organization direkte via plain_connection(). Den løser tomstreng-krasjen, men org_self-policyen krever fortsatt en matchende app.current_org for å vise noen rad i det hele tatt — riktig RLS-design, ikke noe fiksen skulle endre. Siden en bruker kan tilhøre flere organisasjoner, finnes det ingen én kontekst å sette. Rettet til riktig løsning: /auth/me slår nå opp hvert org-navn enkeltvis via org_connection() (verifisert at det faktisk returnerer navnet korrekt).
Alt dokumentert i CLAUDE.md/FEATURE_BACKLOG.md.
2026-07-16 15:34:24 +02:00
|
|
|
-- --- Regresjonstester for RLS-tomstreng-buggen (migrasjon 005) ----------
|
|
|
|
|
-- current_setting('app.current_org', true)::uuid håndterte NULL trygt, men
|
|
|
|
|
-- IKKE tomstreng — en gjenbrukt pool-tilkobling kunne lese GUC-en tilbake
|
|
|
|
|
-- som '' og få en kastet feil i stedet for trygt "se ingenting". Simulerer
|
|
|
|
|
-- tilstanden direkte (set_config med is_local=false, ikke SET LOCAL) siden
|
|
|
|
|
-- den er lettere å fremtvinge deterministisk enn selve pool-gjenbruken som
|
|
|
|
|
-- utløste den i praksis.
|
|
|
|
|
|
|
|
|
|
-- Test 10: tomstreng-GUC — SELECT skal gi 0 rader, IKKE en kastet feil.
|
|
|
|
|
SELECT set_config('app.current_org', '', false);
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE n int;
|
|
|
|
|
BEGIN
|
|
|
|
|
SELECT count(*) INTO n FROM tournament;
|
|
|
|
|
IF n <> 0 THEN
|
|
|
|
|
RAISE EXCEPTION 'ISOLASJON FEILET (tomstreng-lesing): ser % rader, forventet 0', n;
|
|
|
|
|
END IF;
|
|
|
|
|
RAISE NOTICE 'OK Test 10: tomstreng-GUC — SELECT gir trygt 0 rader, ikke feil (%).', n;
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
-- Test 11: tomstreng-GUC — INSERT skal avvises av RLS (insufficient_privilege),
|
|
|
|
|
-- IKKE feile med "invalid input syntax for type uuid".
|
|
|
|
|
DO $$
|
|
|
|
|
BEGIN
|
|
|
|
|
BEGIN
|
ackend for the join-code, leading_side, and projected-standings work is fully verified against a fresh scratch database (migrations 001–011, test_isolation.sql 12/12, and live end-to-end checks: join-code generation/uniqueness, code resolution, visibility bypass on both the tournament view and registration — including case-insensitivity and rejection of a wrong code — plus a full hole-by-hole match simulation confirming leading_side/status_text stay in sync through "1 UP" → "AS" → a decided "9&7", with the leaderboard's projected points matching at each stage: 1.0/0.0 while A led, 0.5/0.5 at all-square, and settling to equal actual/projected once decided).
Ready to deploy to the real system:
Migration: 011_join_code_and_leading_side.sql against real teecup_db (adds tournament.join_code — backfills existing tournaments with generated codes — and match.leading_side, plus the public_tournament_by_code() function).
Redeploy: teecup_api only (no frontend changes yet — those come next).
2026-07-19 09:23:35 +02:00
|
|
|
INSERT INTO tournament (organization_id, name, join_code)
|
|
|
|
|
VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'tomstreng-forsøk', 'TESTX02');
|
RLS-tomstreng-buggen er fikset og verifisert grundig.
Fiksen: ny migrasjon 005_rls_null_guard.sql — én delt STABLE SQL-funksjon app_current_org() (NULLIF(current_setting('app.current_org', true), '')::uuid) erstatter det rå uttrykket i alle 15 RLS-policyer (14 org_isolation + org_self) via ALTER POLICY. NULLIF konverterer tomstreng til NULL før cast, så "trygg standard: se ingenting" gjenopprettes uansett GUC-tilstand.
Verifisert to ganger, ulikt:
Tre nye regresjonstester i test_isolation.sql (Test 10–12): tomstreng-lesing gir 0 rader ikke krasj, tomstreng-skriving avvises av RLS ikke krasj, og org-bootstrap-innsetting (tidligere aldri testet) fungerer rett etter tomstreng-tilstand.
Faktisk gjenskaping av original-buggen mot en ekte container (pool-størrelse tvunget til 1 for å garantere tilkoblings-gjenbruk): varmet opp med et vanlig org_connection()-kall, kalte deretter /auth/me på samme tilkobling — gikk fra 500 til 200.
En viktig ting jeg tok feil om i forrige runde, oppdaget ved å faktisk teste i stedet for å anta: fiksen gjør ikke at /auth/me trygt kan joine organization direkte via plain_connection(). Den løser tomstreng-krasjen, men org_self-policyen krever fortsatt en matchende app.current_org for å vise noen rad i det hele tatt — riktig RLS-design, ikke noe fiksen skulle endre. Siden en bruker kan tilhøre flere organisasjoner, finnes det ingen én kontekst å sette. Rettet til riktig løsning: /auth/me slår nå opp hvert org-navn enkeltvis via org_connection() (verifisert at det faktisk returnerer navnet korrekt).
Alt dokumentert i CLAUDE.md/FEATURE_BACKLOG.md.
2026-07-16 15:34:24 +02:00
|
|
|
RAISE EXCEPTION 'ISOLASJON FEILET (tomstreng-skriving): fikk sette inn data uten gyldig org-kontekst';
|
|
|
|
|
EXCEPTION
|
|
|
|
|
WHEN insufficient_privilege THEN
|
|
|
|
|
RAISE NOTICE 'OK Test 11: tomstreng-GUC — WITH CHECK blokkerte innsetting trygt (ikke krasj).';
|
|
|
|
|
END;
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
-- Test 12: bootstrap-mønster (sett current_org til en FERSK uuid, deretter
|
|
|
|
|
-- INSERT organization med SAMME id) skal fungere korrekt selv RETT ETTER at
|
|
|
|
|
-- tilstanden nettopp var tomstreng — beviser at en eksplisitt satt verdi
|
|
|
|
|
-- alltid overstyrer uansett tidligere GUC-tilstand. Dobler som første test
|
|
|
|
|
-- av selve org-bootstrap-innsettingsstien (002s kommentar, tidligere utestet).
|
|
|
|
|
SELECT set_config('app.current_org', 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', false);
|
|
|
|
|
DO $$
|
|
|
|
|
BEGIN
|
|
|
|
|
INSERT INTO organization (id, name)
|
|
|
|
|
VALUES ('eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', 'Bootstrap-test-org');
|
|
|
|
|
RAISE NOTICE 'OK Test 12: org-bootstrap — INSERT lykkes rett etter tomstreng-tilstand.';
|
|
|
|
|
END $$;
|
|
|
|
|
|
2026-07-16 07:26:04 +02:00
|
|
|
RESET ROLE;
|
|
|
|
|
RESET app.current_org;
|
|
|
|
|
|
|
|
|
|
ROLLBACK; -- ingen testdata blir liggende igjen
|
|
|
|
|
|
|
|
|
|
\echo '============================================'
|
RLS-tomstreng-buggen er fikset og verifisert grundig.
Fiksen: ny migrasjon 005_rls_null_guard.sql — én delt STABLE SQL-funksjon app_current_org() (NULLIF(current_setting('app.current_org', true), '')::uuid) erstatter det rå uttrykket i alle 15 RLS-policyer (14 org_isolation + org_self) via ALTER POLICY. NULLIF konverterer tomstreng til NULL før cast, så "trygg standard: se ingenting" gjenopprettes uansett GUC-tilstand.
Verifisert to ganger, ulikt:
Tre nye regresjonstester i test_isolation.sql (Test 10–12): tomstreng-lesing gir 0 rader ikke krasj, tomstreng-skriving avvises av RLS ikke krasj, og org-bootstrap-innsetting (tidligere aldri testet) fungerer rett etter tomstreng-tilstand.
Faktisk gjenskaping av original-buggen mot en ekte container (pool-størrelse tvunget til 1 for å garantere tilkoblings-gjenbruk): varmet opp med et vanlig org_connection()-kall, kalte deretter /auth/me på samme tilkobling — gikk fra 500 til 200.
En viktig ting jeg tok feil om i forrige runde, oppdaget ved å faktisk teste i stedet for å anta: fiksen gjør ikke at /auth/me trygt kan joine organization direkte via plain_connection(). Den løser tomstreng-krasjen, men org_self-policyen krever fortsatt en matchende app.current_org for å vise noen rad i det hele tatt — riktig RLS-design, ikke noe fiksen skulle endre. Siden en bruker kan tilhøre flere organisasjoner, finnes det ingen én kontekst å sette. Rettet til riktig løsning: /auth/me slår nå opp hvert org-navn enkeltvis via org_connection() (verifisert at det faktisk returnerer navnet korrekt).
Alt dokumentert i CLAUDE.md/FEATURE_BACKLOG.md.
2026-07-16 15:34:24 +02:00
|
|
|
\echo ' Alle 12 isolasjonstester bestått (se OK-linjer)'
|
|
|
|
|
\echo ' Dekker: tournament/organization (001),'
|
|
|
|
|
\echo ' match_hole_result/lineup_lock (003, ADR-012/013),'
|
|
|
|
|
\echo ' og RLS-tomstreng-fiksen (005)'
|
2026-07-16 07:26:04 +02:00
|
|
|
\echo '============================================'
|