play data-safety: write the conditional required rows (PSL_ACCOUNT_DELETION_URL from accounts.deletionUrl, PSL_SUPPORT_DATA_DELETION_BY_USER from dataSafety.partialDeletion, PSL_DATA_DELETION_URL); a rule table checks every conditional row in check, declarations and --dry before Play answers 400

This commit is contained in:
CRTkafa 2026-10-12 08:25:14 +08:00
parent c9a7f095c4
commit 97ab76e9c8
14 changed files with 295 additions and 24 deletions

View file

@ -347,6 +347,21 @@ describe('listing, appinfo, data safety and the owner commands: a second run wri
expect(s.play.dataSafety).toHaveLength(3);
});
test('play data-safety: a missing conditional row stops --dry and the real run before any POST', async () => {
// STAR DASH: accounts with a username, no deletion URL in the CSV -> Play 400 "Response missing for PSL_ACCOUNT_DELETION_URL"
const ds = { collectsData: true, encryptedInTransit: true, deletionRequest: true, accountCreation: ['PSL_ACM_USER_ID_OTHER_AUTH'], partialDeletion: false,
data: { PSL_USER_ACCOUNT: { shared: false, collect: ['account'] } } };
const p = project({ ...CONFIG, accounts: { enabled: true, signUp: ['username'] }, dataSafety: ds as never });
const before = s.calls.length;
await expect(capture(() => playDataSafety(p.ctx(['--dry'])))).rejects.toThrow('missing: PSL_ACCOUNT_DELETION_URL (account creation is not PSL_ACM_NONE; set accounts.deletionUrl');
await expect(capture(() => playDataSafety(p.ctx([])))).rejects.toThrow('PSL_ACCOUNT_DELETION_URL');
expect(writes(s.calls.slice(before))).toEqual([]);
const q = project({ ...CONFIG, accounts: { enabled: true, signUp: ['username'], deletionUrl: 'https://example.com/delete-account' }, dataSafety: ds as never });
await capture(() => playDataSafety(q.ctx([])));
expect(s.play.dataSafety.at(-1)).toContain('PSL_ACCOUNT_DELETION_URL,,https://example.com/delete-account,');
expect(s.play.dataSafety.at(-1)).toContain('PSL_SUPPORT_DATA_DELETION_BY_USER,,FALSE,');
});
test('apple promoted: created and ordered once', async () => {
s.asc.put({ type: 'inAppPurchases', id: 'iap-pro', attributes: { productId: 'com.example.notes.pro', state: 'APPROVED' }, rels: { app: APP } });
s.asc.put({ type: 'inAppPurchases', id: 'iap-tip', attributes: { productId: 'com.example.notes.tip', state: 'APPROVED' }, rels: { app: APP } });