Shuffle/supabase/tests/database/05_auth_registration.test.sql
cschlaefke fb0f4b9d3f
All checks were successful
CI / flutter (pull_request) Successful in 1m3s
CI / web-build (pull_request) Successful in 58s
CI / supabase (pull_request) Successful in 3s
CI / supabase-tests (pull_request) Successful in 34s
fix(db): trim all Unicode whitespace in display name, harden templates
- add public.trim_ws mirroring Dart String.trim
- require JSON string metadata, tolerate non-string locale in templates
- add tests, align docs with review

Refs #1

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 23:48:01 +01:00

247 lines
18 KiB
PL/PgSQL

-- KTO-01 Registrierung (Datenbankseite): Regeln des Triggers handle_new_user, Anzeigename,
-- Nachweis der Zustimmung (terms_version), Sprache und Mailsprache-Sync.
begin;
select plan(60);
-- ---------------------------------------------------------------------------------------------
-- KTO-01 Kriterium 1: gültige Registrierung legt ein Profil an
-- ---------------------------------------------------------------------------------------------
select lives_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values
('11111111-1111-1111-1111-111111111111', 'anna@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"2026-11-draft","display_name":" Anna ","locale":"de"}')$$,
'KTO-01 Kriterium 1: gültige Registrierung wird angenommen');
select is((select display_name from public.profiles where id = '11111111-1111-1111-1111-111111111111'),
'Anna', 'KTO-01 Kriterium 1: Anzeigename wird getrimmt gespeichert');
select is((select locale from public.profiles where id = '11111111-1111-1111-1111-111111111111'),
'de', 'KTO-01 Kriterium 11: Sprache aus der Registrierung wird übernommen');
select is((select terms_version from public.profiles where id = '11111111-1111-1111-1111-111111111111'),
'2026-11-draft', 'KTO-01 Kriterium 1: terms_version wird gespeichert (QUE-04 Nachweis)');
select ok((select terms_accepted_at is not null and age_confirmed_at is not null
and terms_accepted_at = now() and age_confirmed_at = now()
from public.profiles where id = '11111111-1111-1111-1111-111111111111'),
'KTO-01 Kriterium 1: Zeitpunkte von Zustimmung und Altersbestätigung sind gesetzt');
-- Der Client sendet JSON-Booleans (kein String): beide Formen müssen gelten.
select lives_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values
('88888888-8888-8888-8888-888888888888', 'bool@example.com',
'{"age_confirmed":true,"terms_accepted":true,"terms_version":"v1","display_name":"Bool"}')$$,
'KTO-01 Kriterium 1: JSON-Booleans true werden angenommen');
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'k2c@example.com',
'{"age_confirmed":false,"terms_accepted":true,"terms_version":"v1","display_name":"X"}')$$,
'P0001', 'age_confirmation_required', 'KTO-01 Kriterium 2: JSON-Boolean age_confirmed=false wird abgelehnt');
-- ---------------------------------------------------------------------------------------------
-- KTO-01 Kriterium 2: Mindestalter muss bestätigt sein
-- ---------------------------------------------------------------------------------------------
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'k2a@example.com',
'{"age_confirmed":"false","terms_accepted":"true","terms_version":"v1","display_name":"X"}')$$,
'P0001', 'age_confirmation_required', 'KTO-01 Kriterium 2: age_confirmed=false wird abgelehnt');
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'k2b@example.com',
'{"terms_accepted":"true","terms_version":"v1","display_name":"X"}')$$,
'P0001', 'age_confirmation_required', 'KTO-01 Kriterium 2: fehlende Altersbestätigung wird abgelehnt');
select is((select count(*) from auth.users where email like 'k2%@example.com'), 0::bigint,
'KTO-01 Kriterium 2: abgelehnte Registrierung legt kein Konto an');
-- ---------------------------------------------------------------------------------------------
-- QUE-04 Kriterium 1: ausdrückliche Zustimmung
-- ---------------------------------------------------------------------------------------------
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'q1@example.com',
'{"age_confirmed":"true","terms_accepted":"false","terms_version":"v1","display_name":"X"}')$$,
'P0001', 'terms_acceptance_required', 'QUE-04 Kriterium 1: terms_accepted=false wird abgelehnt');
-- terms_version Pflicht (Nachweis F5)
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'tv1@example.com',
'{"age_confirmed":"true","terms_accepted":"true","display_name":"X"}')$$,
'P0001', 'terms_version_required', 'QUE-04 Nachweis: fehlende terms_version wird abgelehnt');
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'tv2@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"","display_name":"X"}')$$,
'P0001', 'terms_version_required', 'QUE-04 Nachweis: leere terms_version wird abgelehnt');
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'tv3@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":" ","display_name":"X"}')$$,
'P0001', 'terms_version_required', 'QUE-04 Nachweis: terms_version nur aus Leerzeichen wird abgelehnt');
select throws_ok(
format($$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'tv4@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"%s","display_name":"X"}')$$, repeat('v', 51)),
'P0001', 'terms_version_required', 'QUE-04 Nachweis: terms_version mit 51 Zeichen wird abgelehnt');
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'tv6@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"\u00a0","display_name":"X"}')$$,
'P0001', 'terms_version_required', 'QUE-04 Nachweis: terms_version nur aus NBSP wird abgelehnt');
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'tv7@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":5,"display_name":"X"}')$$,
'P0001', 'terms_version_required', 'QUE-04 Nachweis: terms_version als Zahl wird abgelehnt');
select lives_ok(
format($$insert into auth.users (id, email, raw_user_meta_data) values
('55555555-5555-5555-5555-555555555555', 'tv5@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":" %s ","display_name":"X"}')$$, repeat('v', 50)),
'QUE-04 Nachweis: terms_version mit 50 Zeichen (getrimmt) wird angenommen');
select is((select char_length(terms_version) from public.profiles where id = '55555555-5555-5555-5555-555555555555'),
50, 'QUE-04 Nachweis: terms_version wird getrimmt gespeichert');
-- ---------------------------------------------------------------------------------------------
-- KTO-01 Kriterium 10: Anzeigename 1-30 Zeichen, streng
-- ---------------------------------------------------------------------------------------------
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'n1@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1"}')$$,
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: fehlender Anzeigename wird abgelehnt (kein E-Mail-Präfix)');
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'n2@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":""}')$$,
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: leerer Anzeigename wird abgelehnt');
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'n3@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":" "}')$$,
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: Anzeigename nur aus Leerzeichen wird abgelehnt');
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'n4@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":null}')$$,
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: Anzeigename null wird abgelehnt');
select throws_ok(
format($$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'n5@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"%s"}')$$, repeat('a', 31)),
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: Anzeigename mit 31 Zeichen wird abgelehnt (kein Kürzen)');
select throws_ok(
format($$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'n6@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"%s"}')$$, repeat('😀', 31)),
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: 31 Emoji (Codepoints) werden abgelehnt');
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'n9@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"\t"}')$$,
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: Anzeigename nur aus Tab wird abgelehnt');
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'n10@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"\n"}')$$,
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: Anzeigename nur aus Zeilenumbruch wird abgelehnt');
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'n11@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"\u00a0"}')$$,
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: Anzeigename nur aus NBSP wird abgelehnt');
select throws_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'n12@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":5}')$$,
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: Anzeigename als Zahl wird abgelehnt');
select is((select count(*) from auth.users where email ~ '^n[0-9]+@example.com$'), 0::bigint,
'KTO-01 Kriterium 10: abgelehnte Registrierungen legen kein Konto an');
select lives_ok(
format($$insert into auth.users (id, email, raw_user_meta_data) values
('66666666-6666-6666-6666-666666666666', 'n7@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"%s"}')$$, repeat('😀', 30)),
'KTO-01 Kriterium 10: 30 Emoji (30 Codepoints, 120 Bytes) werden angenommen');
select lives_ok(
format($$insert into auth.users (id, email, raw_user_meta_data) values
('77777777-7777-7777-7777-777777777777', 'n8@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":" %s "}')$$, repeat('b', 30)),
'KTO-01 Kriterium 10: 30 Zeichen mit umgebenden Leerzeichen werden nach dem Trim angenommen');
select is((select display_name from public.profiles where id = '77777777-7777-7777-7777-777777777777'),
repeat('b', 30), 'KTO-01 Kriterium 10: der Name wird ohne Leerraum gespeichert');
select lives_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values
('99999999-9999-9999-9999-999999999999', 'n13@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"Anna\t"}')$$,
'KTO-01 Kriterium 10: Anzeigename mit Tab am Ende wird angenommen');
select is((select display_name from public.profiles where id = '99999999-9999-9999-9999-999999999999'),
'Anna', 'KTO-01 Kriterium 10: Tab wird entfernt, gespeichert wird Anna');
-- Check-Constraint (gilt auch für spätere Änderungen, KTO-02)
select ok(exists (select 1 from pg_constraint
where conrelid = 'public.profiles'::regclass and conname = 'profiles_display_name_not_blank'
and contype = 'c' and convalidated),
'KTO-01 Kriterium 10: Constraint profiles_display_name_not_blank existiert und ist validiert');
select throws_ok(
$$update public.profiles set display_name = ' ' where id = '11111111-1111-1111-1111-111111111111'$$,
'23514', null, 'KTO-01 Kriterium 10: Constraint lehnt Anzeigename nur aus Leerzeichen ab (Update)');
select throws_ok(
$$update public.profiles set display_name = chr(160) where id = '11111111-1111-1111-1111-111111111111'$$,
'23514', null, 'KTO-02 Kriterium 1: Constraint lehnt Anzeigename nur aus NBSP ab (Update)');
-- ---------------------------------------------------------------------------------------------
-- KTO-01 Kriterium 11: Sprache
-- ---------------------------------------------------------------------------------------------
select lives_ok(
$$insert into auth.users (id, email, raw_user_meta_data) values
('22222222-2222-2222-2222-222222222222', 'ben@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"Ben"}'),
('33333333-3333-3333-3333-333333333333', 'cara@example.com',
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"Cara","locale":"fr"}')$$,
'KTO-01 Kriterium 11: Registrierungen ohne und mit ungültiger Sprache werden angenommen');
select is((select locale from public.profiles where id = '22222222-2222-2222-2222-222222222222'),
'en', 'KTO-01 Kriterium 11: ohne locale gilt Englisch');
select is((select locale from public.profiles where id = '33333333-3333-3333-3333-333333333333'),
'en', 'KTO-01 Kriterium 11: ungültige locale fällt auf Englisch zurück');
select col_default_is('public', 'profiles', 'locale', 'en', 'KTO-01 Kriterium 11: Spalten-Standard ist en');
-- Sync profiles.locale -> auth.users.raw_user_meta_data.locale
select is((select raw_user_meta_data ->> 'locale' from auth.users where id = '11111111-1111-1111-1111-111111111111'),
'de', 'KTO-01 Kriterium 11: Ausgangszustand der Mail-Sprache (Anna de)');
-- Marker setzen: Eine Änderung anderer Spalten darf den Sync nicht auslösen
update auth.users set raw_user_meta_data = raw_user_meta_data || '{"locale":"xx"}'
where id = '11111111-1111-1111-1111-111111111111';
select set_config('request.jwt.claims', '{"sub":"11111111-1111-1111-1111-111111111111","role":"authenticated"}', true);
set local role authenticated;
select lives_ok($$update public.profiles set bio = 'nur Bio' where id = '11111111-1111-1111-1111-111111111111'$$,
'KTO-01 Kriterium 11: Anna darf ihre Bio ändern');
reset role;
select is((select raw_user_meta_data ->> 'locale' from auth.users where id = '11111111-1111-1111-1111-111111111111'),
'xx', 'KTO-01 Kriterium 11: Änderung einer anderen Spalte löst den Sync nicht aus');
set local role authenticated;
select lives_ok($$update public.profiles set locale = 'en' where id = '11111111-1111-1111-1111-111111111111'$$,
'KTO-01 Kriterium 11: Anna darf ihre Sprache ändern');
select is_empty($$update public.profiles set locale = 'de' where id = '22222222-2222-2222-2222-222222222222' returning 1$$,
'KTO-01 Kriterium 11: Anna kann die Sprache von Ben nicht ändern');
select throws_ok($$update auth.users set raw_user_meta_data = '{}' where id = '11111111-1111-1111-1111-111111111111'$$,
'42501', null, 'KTO-01 Kriterium 11: Nutzer kann auth.users nicht direkt schreiben');
select throws_ok($$select public.sync_profile_locale_to_auth()$$,
'42501', null, 'KTO-01 Kriterium 11: Sync-Funktion ist für Nutzer nicht aufrufbar');
select throws_ok($$select public.handle_new_user()$$,
'42501', null, 'KTO-01 Kriterium 1: handle_new_user ist für Nutzer nicht aufrufbar');
-- Sicherheit: Zustimmungsnachweis ist für den Nutzer unveränderlich
select throws_ok($$update public.profiles set terms_version = 'gefälscht' where id = '11111111-1111-1111-1111-111111111111'$$,
'42501', null, 'KTO-01 Sicherheit: Nutzer kann terms_version nicht ändern');
select throws_ok($$update public.profiles set terms_accepted_at = now() where id = '11111111-1111-1111-1111-111111111111'$$,
'42501', null, 'KTO-01 Sicherheit: Nutzer kann terms_accepted_at nicht ändern');
select is((select terms_version from public.profiles where id = '11111111-1111-1111-1111-111111111111'),
'2026-11-draft', 'KTO-01 Sicherheit: Nutzer kann seine terms_version lesen');
select is((select count(*) from public.profiles), 1::bigint, 'KTO-01 Sicherheit: Nutzer sieht nur das eigene Profil');
reset role;
select set_config('request.jwt.claims', '', true);
select is((select raw_user_meta_data ->> 'locale' from auth.users where id = '11111111-1111-1111-1111-111111111111'),
'en', 'KTO-01 Kriterium 11: Profilsprache wird in die Mail-Sprache übernommen');
select is((select raw_user_meta_data ->> 'display_name' from auth.users where id = '11111111-1111-1111-1111-111111111111'),
' Anna ', 'KTO-01 Kriterium 11: übrige Metadaten bleiben beim Sync erhalten');
select is((select raw_user_meta_data ->> 'locale' from auth.users where id = '22222222-2222-2222-2222-222222222222'),
null, 'KTO-01 Kriterium 11: Metadaten fremder Konten bleiben unverändert');
-- anon darf die Trigger-Funktionen nicht ausführen
set local role anon;
select throws_ok($$select public.handle_new_user()$$,
'42501', null, 'KTO-01 Sicherheit: anon darf handle_new_user nicht ausführen');
select throws_ok($$select public.sync_profile_locale_to_auth()$$,
'42501', null, 'KTO-01 Sicherheit: anon darf sync_profile_locale_to_auth nicht ausführen');
reset role;
-- Update auf denselben Wert löst keinen Sync aus (Cara: locale en, Marker in den Metadaten)
update auth.users set raw_user_meta_data = raw_user_meta_data || '{"locale":"xx"}'
where id = '33333333-3333-3333-3333-333333333333';
update public.profiles set locale = 'en' where id = '33333333-3333-3333-3333-333333333333';
select is((select raw_user_meta_data ->> 'locale' from auth.users where id = '33333333-3333-3333-3333-333333333333'),
'xx', 'KTO-01 Kriterium 11: Update auf denselben Wert löst den Sync nicht aus');
select * from finish();
rollback;