- 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>
247 lines
18 KiB
PL/PgSQL
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;
|