Skip to content

Commit 7e9040d

Browse files
committed
test(platform-objects): pin the rotate-secret cut-off wording in source and every locale
Claude-Session: https://claude.ai/code/session_01MrvwG9BMeMc4oCSG9FsV95 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 82095d3 commit 7e9040d

2 files changed

Lines changed: 79 additions & 1 deletion

File tree

‎packages/platform-objects/src/apps/translations/en.objects.generated.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1557,7 +1557,7 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
15571557
description: "Rotate this OAuth client's secret? The previous secret will stop working immediately and any integrations using it will break until they are updated with the new secret. The new secret is shown only once.",
15581558
resultDialog: {
15591559
title: "Client secret rotated",
1560-
description: "Save the new secret now — it is shown only once. Update every integration before the previous secret's grace period ends.",
1560+
description: "Save the new secret now — it is shown only once. The previous secret has already stopped working: update every integration with the new secret.",
15611561
acknowledge: "I have updated my integrations",
15621562
fields: {
15631563
client_secret: "New Client Secret"
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* `sys_oauth_application.rotate_client_secret` — the confirmation and the
5+
* result dialog say the same thing about the previous secret.
6+
*
7+
* The two texts once disagreed: the confirmation said the previous secret
8+
* "will stop working immediately", the result dialog told the admin to update
9+
* integrations "before the previous secret's grace period ends". Only the
10+
* first is true. The route is better-auth's own
11+
* (`@better-auth/oauth-provider` 1.7.3, `rotateClientSecretEndpoint`): it
12+
* overwrites the client's single stored `clientSecret`, and
13+
* `validateClientCredentials` checks a presented secret against that one value.
14+
* No previous secret is kept, so no grace period exists.
15+
*
16+
* The wording IS the contract here — it is what an admin acts on before
17+
* breaking every integration — so it is pinned in the source and in all four
18+
* shipped locales. The translated leaves are hand-written: `pnpm i18n:extract`
19+
* keeps them in merge mode and the source-hash table holds no entry for them
20+
* (they are real translations, not byte copies), so a stale translation of an
21+
* edited source is not flagged anywhere else.
22+
*/
23+
24+
import { describe, expect, it } from 'vitest';
25+
import { SysOauthApplication } from './index.js';
26+
import { enObjects } from '../apps/translations/en.objects.generated.js';
27+
import { zhCNObjects } from '../apps/translations/zh-CN.objects.generated.js';
28+
import { jaJPObjects } from '../apps/translations/ja-JP.objects.generated.js';
29+
import { esESObjects } from '../apps/translations/es-ES.objects.generated.js';
30+
31+
const rotate = (SysOauthApplication.actions ?? []).find((a) => a.name === 'rotate_client_secret');
32+
const node = (bundle: any) => bundle?.sys_oauth_application?._actions?.rotate_client_secret;
33+
34+
/** Any promise of a window during which the previous secret still works. */
35+
const GRACE_TERMS: readonly (readonly [string, any, RegExp])[] = [
36+
['en', enObjects, /grace|window|expire/i],
37+
['zh-CN', zhCNObjects, /宽限|过渡期|有效期/],
38+
['ja-JP', jaJPObjects, /猶予|移行期間|有効期限/],
39+
['es-ES', esESObjects, /gracia|transición|caduque|expire/i],
40+
];
41+
42+
describe('rotate_client_secret — both texts state the immediate cut-off', () => {
43+
it('the source confirmation and result dialog both say the previous secret stops at once', () => {
44+
expect(rotate, 'rotate_client_secret is declared').toBeDefined();
45+
const confirm = rotate?.description ?? '';
46+
const result = rotate?.resultDialog?.description ?? '';
47+
expect(confirm).toMatch(/previous secret will stop working immediately/);
48+
expect(result).toMatch(/previous secret has already stopped working/);
49+
for (const [label, text] of [['description', confirm], ['resultDialog.description', result]] as const) {
50+
expect(text, `${label} promises a grace period the route does not keep`).not.toMatch(/grace|window|expire/i);
51+
}
52+
});
53+
54+
it('the result dialog keeps its shown-once warning, acknowledgement and secret field', () => {
55+
expect(rotate?.resultDialog?.description).toMatch(/^Save the new secret now — it is shown only once\./);
56+
expect(rotate?.resultDialog?.acknowledge).toBe('I have updated my integrations');
57+
expect(rotate?.resultDialog?.fields?.map((f) => [f.path, f.format])).toEqual([['client_secret', 'secret']]);
58+
});
59+
60+
it('the English bundle mirrors the source', () => {
61+
expect(node(enObjects)?.description).toBe(rotate?.description);
62+
expect(node(enObjects)?.resultDialog?.description).toBe(rotate?.resultDialog?.description);
63+
});
64+
65+
it('no locale promises a grace period in either text, and each translates the result dialog', () => {
66+
for (const [locale, bundle, grace] of GRACE_TERMS) {
67+
const n = node(bundle);
68+
expect(n?.description, `${locale} description`).toBeTruthy();
69+
expect(n?.resultDialog?.description, `${locale} resultDialog.description`).toBeTruthy();
70+
expect(n.description, `${locale} description promises a grace period`).not.toMatch(grace);
71+
expect(n.resultDialog.description, `${locale} resultDialog.description promises a grace period`).not.toMatch(grace);
72+
if (locale !== 'en') {
73+
expect(n.resultDialog.description, `${locale} resultDialog.description is the English source`)
74+
.not.toBe(node(enObjects)?.resultDialog?.description);
75+
}
76+
}
77+
});
78+
});

0 commit comments

Comments
 (0)