package onboarding import ( "fmt" "strings" "testing" "data-terminal-cloud/internal/audit" "data-terminal-cloud/internal/saas" "github.com/google/uuid" ) func TestProfileCreationRollsBackWhenMemoryConsentFails(t *testing.T) { f := newFixture(t) name := "test_initial_memory_" + strings.ReplaceAll(uuid.NewString(), "-", "") // Fail the second grant for this synthetic tenant to verify that the first // grant, the new household, profile, audit records and replay result all roll back. _, err := f.s.pool.Exec(f.ctx, fmt.Sprintf(`CREATE FUNCTION %s() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF NEW.tenant_id='%s'::uuid AND NEW.consent_type='personalized_memory' THEN RAISE EXCEPTION 'synthetic consent failure'; END IF; RETURN NEW; END $$; CREATE TRIGGER %s BEFORE INSERT ON consent_record FOR EACH ROW EXECUTE FUNCTION %s();`, name, f.tenant, name, name)) must(t, err) t.Cleanup(func() { _, _ = f.s.pool.Exec(f.ctx, fmt.Sprintf("DROP TRIGGER IF EXISTS %s ON consent_record; DROP FUNCTION IF EXISTS %s();", name, name)) }) in := ProfileInput{HouseholdName: "授权失败家庭", Name: "授权失败老人", InitialConsents: saas.InitialConsentSelection{Types: []string{saas.ConsentTranscriptStorage, saas.ConsentPersonalMemory}, Confirmed: true}} requestID := uuid.NewString() _, err = f.s.CreateProfile(f.ctx, f.web, requestID, requestJSON(in), in) if err == nil { t.Fatal("profile creation succeeded despite failed consent") } var profiles, households, audits int must(t, f.s.pool.QueryRow(f.ctx, `SELECT (SELECT count(*) FROM subject_profile WHERE tenant_id=$1::uuid AND preferred_name=$2), (SELECT count(*) FROM household WHERE tenant_id=$1::uuid AND name=$3), (SELECT count(*) FROM audit_event WHERE request_id=$4)`, f.tenant, in.Name, in.HouseholdName, requestID).Scan(&profiles, &households, &audits)) if profiles != 0 || households != 0 || audits != 0 { t.Fatalf("partial creation survived: profiles=%d households=%d audits=%d", profiles, households, audits) } _, err = f.s.pool.Exec(f.ctx, fmt.Sprintf("DROP TRIGGER %s ON consent_record; DROP FUNCTION %s();", name, name)) must(t, err) _, err = f.s.CreateProfile(f.ctx, f.web, requestID, requestJSON(in), in) must(t, err) } func TestProfileCreationIncludesMemoryConsentAndReplay(t *testing.T) { f := newFixture(t) in := ProfileInput{HouseholdName: "选项授权测试家庭", Name: "选项授权测试老人", InitialConsents: saas.InitialConsentSelection{Types: []string{saas.ConsentTranscriptStorage, saas.ConsentPersonalMemory}, Confirmed: true}} requestID := uuid.NewString() created, err := f.s.CreateProfile(f.ctx, f.web, requestID, requestJSON(in), in) must(t, err) replayed, err := f.s.CreateProfile(f.ctx, f.web, requestID, requestJSON(in), in) must(t, err) if replayed["elder_id"] != created["elder_id"] { t.Fatal("profile replay created another elder") } var count, audits int var enabled bool must(t, f.s.pool.QueryRow(f.ctx, `SELECT (SELECT count(*) FROM consent_record c WHERE c.subject_id=sp.id), (SELECT count(*) FROM audit_event WHERE request_id=$2 AND action='consent.grant'), (SELECT effective_consents @> '{"voice_chat":true,"transcript_storage":true,"personalized_memory":true,"raw_audio_storage":false,"story_extraction":false,"family_sharing":false}'::jsonb AND jsonb_array_length(source_consent_ids)=2 FROM consent_snapshot WHERE subject_id=sp.id ORDER BY snapshot_version DESC LIMIT 1) FROM subject_profile sp WHERE sp.public_id=$1::uuid`, created["elder_id"], requestID).Scan(&count, &audits, &enabled)) if count != 2 || audits != 2 || !enabled { t.Fatalf("default consents count=%d audits=%d enabled=%v", count, audits, enabled) } } func TestMemoryWithdrawalSurvivesDeviceBinding(t *testing.T) { f := newFixture(t) api := saas.NewService(f.s.pool, audit.NewRepository(), nil, nil) consents, err := api.ListConsents(f.ctx, f.user, f.elder) must(t, err) for _, c := range consents { _, err = api.WithdrawConsent(f.ctx, f.user, f.elder, c.ID, uuid.NewString()) must(t, err) } f.bound() var enabled bool must(t, f.s.pool.QueryRow(f.ctx, `SELECT call_memory_authorized(id) FROM subject_profile WHERE public_id=$1::uuid`, f.elder).Scan(&enabled)) if enabled { t.Fatal("device binding restored withdrawn memory consent") } } func TestCreationGrantsOnlyExplicitlySelectedPermissions(t *testing.T) { f := newFixture(t) for _, tc := range []struct { name string selected []string }{ {"legacy", nil}, {"subtitle_only", []string{saas.ConsentTranscriptStorage}}, {"all", []string{saas.ConsentTranscriptStorage, saas.ConsentRawAudioStorage, saas.ConsentStoryExtraction, saas.ConsentPersonalMemory, saas.ConsentFamilySharing}}, } { t.Run(tc.name, func(t *testing.T) { in := ProfileInput{HouseholdName: "授权选择测试" + tc.name, Name: "测试老人", InitialConsents: saas.InitialConsentSelection{Types: tc.selected, Confirmed: len(tc.selected) > 0}} created, err := f.s.CreateProfile(f.ctx, f.web, uuid.NewString(), requestJSON(in), in) must(t, err) var kinds []string var sourceCount int must(t, f.s.pool.QueryRow(f.ctx, `SELECT ARRAY(SELECT consent_type FROM consent_record WHERE subject_id=sp.id ORDER BY consent_type), (SELECT jsonb_array_length(source_consent_ids) FROM consent_snapshot WHERE subject_id=sp.id ORDER BY snapshot_version DESC LIMIT 1) FROM subject_profile sp WHERE public_id=$1::uuid`, created["elder_id"]).Scan(&kinds, &sourceCount)) if len(kinds) != len(tc.selected) || sourceCount != len(tc.selected) { t.Fatalf("grants=%v sourceCount=%d wanted=%v", kinds, sourceCount, tc.selected) } for _, kind := range kinds { found := false for _, selected := range tc.selected { found = found || kind == selected } if !found { t.Fatalf("unselected permission granted: %s", kind) } } }) } bad := ProfileInput{HouseholdName: "未确认家庭", Name: "未确认老人", InitialConsents: saas.InitialConsentSelection{Types: []string{saas.ConsentPersonalMemory}}} if _, err := f.s.CreateProfile(f.ctx, f.web, uuid.NewString(), requestJSON(bad), bad); err == nil { t.Fatal("unconfirmed consent was accepted") } var count int must(t, f.s.pool.QueryRow(f.ctx, `SELECT count(*) FROM household WHERE tenant_id=$1::uuid AND name=$2`, f.tenant, bad.HouseholdName).Scan(&count)) if count != 0 { t.Fatal("unconfirmed selection left an orphan household") } }