diff --git a/packages/core/src/routes/custom-phrase.ts b/packages/core/src/routes/custom-phrase.ts index be56cf943..d1c4dcc72 100644 --- a/packages/core/src/routes/custom-phrase.ts +++ b/packages/core/src/routes/custom-phrase.ts @@ -34,6 +34,7 @@ export default function customPhraseRoutes( '/custom-phrases', koaGuard({ response: CustomPhrases.guard.array(), + status: [200], }), async (ctx, next) => { ctx.body = await findAllCustomPhrases(); @@ -47,6 +48,7 @@ export default function customPhraseRoutes( koaGuard({ params: object({ languageTag: languageTagGuard }), response: CustomPhrases.guard, + status: [200], }), async (ctx, next) => { const { @@ -65,6 +67,7 @@ export default function customPhraseRoutes( params: object({ languageTag: languageTagGuard }), body: translationGuard, response: CustomPhrases.guard, + status: [200], }), async (ctx, next) => { const { @@ -89,6 +92,7 @@ export default function customPhraseRoutes( '/custom-phrases/:languageTag', koaGuard({ params: object({ languageTag: languageTagGuard }), + status: [204], }), async (ctx, next) => { const { diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index c626d9e0e..c7cdbf0f7 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -23,6 +23,7 @@ "devDependencies": { "@jest/types": "^29.1.2", "@logto/connector-kit": "workspace:^1.1.0", + "@logto/language-kit": "workspace:^1.0.0", "@logto/js": "^2.0.1", "@logto/node": "^2.0.0", "@logto/schemas": "workspace:^1.1.0", diff --git a/packages/integration-tests/src/api/custom-phrase.ts b/packages/integration-tests/src/api/custom-phrase.ts new file mode 100644 index 000000000..6e2beb264 --- /dev/null +++ b/packages/integration-tests/src/api/custom-phrase.ts @@ -0,0 +1,17 @@ +import type { CustomPhrase, Translation } from '@logto/schemas'; + +import { authedAdminApi } from './api.js'; + +export const listCustomPhrases = async () => + authedAdminApi.get('custom-phrases').json(); + +export const getCustomPhrase = async (languageTag: string) => + authedAdminApi.get(`custom-phrases/${languageTag}`).json(); + +export const createOrUpdateCustomPhrase = async (languageTag: string, translation: Translation) => + authedAdminApi + .put({ url: `custom-phrases/${languageTag}`, json: translation }) + .json(); + +export const deleteCustomPhrase = async (languageTag: string) => + authedAdminApi.delete(`custom-phrases/${languageTag}`).json(); diff --git a/packages/integration-tests/src/tests/api/custom-phrase.test.ts b/packages/integration-tests/src/tests/api/custom-phrase.test.ts new file mode 100644 index 000000000..133605f7b --- /dev/null +++ b/packages/integration-tests/src/tests/api/custom-phrase.test.ts @@ -0,0 +1,96 @@ +import { HTTPError } from 'got'; + +import { + listCustomPhrases, + getCustomPhrase, + createOrUpdateCustomPhrase, + deleteCustomPhrase, +} from '#src/api/custom-phrase.js'; + +const mockZhTranslation = { + input: { + email: '邮箱', + password: '密码', + username: '用户名', + phone_number: '手机号码', + confirm_password: '确认密码', + search_region_code: '搜索区号', + }, +}; + +const mockZhTranslationUpdated = { + ...mockZhTranslation, + description: { + email: '邮箱', + phone_number: '手机号码', + }, +}; + +const mockEnUsTranslation = { + input: { + email: 'email', + password: 'password', + username: 'username', + phone_number: 'phone_number', + confirm_password: 'confirm password', + search_region_code: 'search region code', + }, +}; + +describe('custom-phrase flow', () => { + it('failed create a non built-in custom phrase with invalid language tag (zh-ZH)', async () => { + await expect(createOrUpdateCustomPhrase('zh-ZH', mockZhTranslation)).rejects.toThrow(HTTPError); + }); + + it('create a non built-in custom phrase (zh)', async () => { + await createOrUpdateCustomPhrase('zh', mockZhTranslation); + const { translation: zhTranslation } = await getCustomPhrase('zh'); + expect(zhTranslation).toEqual(mockZhTranslation); + }); + + it('customize a built-in custom phrase (en-US)', async () => { + await createOrUpdateCustomPhrase('en-US', mockEnUsTranslation); + const { translation: enUsTranslation } = await getCustomPhrase('en-US'); + expect(enUsTranslation).toEqual(mockEnUsTranslation); + }); + + it('update an existing custom phrase', async () => { + await createOrUpdateCustomPhrase('zh', mockZhTranslationUpdated); + const { translation: zhTranslationNew } = await getCustomPhrase('zh'); + expect(zhTranslationNew).toEqual(mockZhTranslationUpdated); + }); + + it('failed to get a custom phrase with invalid language tag (zh-ZH)', async () => { + await expect(getCustomPhrase('zh-ZH')).rejects.toThrow(HTTPError); + }); + + it('failed to get a custom phrase with non-existing record', async () => { + await expect(getCustomPhrase('zh-TW')).rejects.toThrow(HTTPError); + }); + + it('get all custom phrases', async () => { + const allCustomPhrases = await listCustomPhrases(); + expect(allCustomPhrases.find(({ languageTag }) => languageTag === 'zh')?.translation).toEqual( + mockZhTranslationUpdated + ); + expect( + allCustomPhrases.find(({ languageTag }) => languageTag === 'en-US')?.translation + ).toEqual(mockEnUsTranslation); + }); + + it('failed to delete a custom phrase with invalid language tag (zh-ZH)', async () => { + await expect(deleteCustomPhrase('zh-ZH')).rejects.toThrow(HTTPError); + }); + + it('failed to delete a custom phrase with non-existing record', async () => { + await expect(deleteCustomPhrase('zh-TW')).rejects.toThrow(HTTPError); + }); + + it('delete all custom phrases', async () => { + await deleteCustomPhrase('zh'); + await deleteCustomPhrase('en-US'); + const allCustomPhrases = await listCustomPhrases(); + expect(allCustomPhrases.find(({ languageTag }) => languageTag === 'zh')).toBeUndefined(); + expect(allCustomPhrases.find(({ languageTag }) => languageTag === 'en-US')).toBeUndefined(); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d0b63539e..9321d490f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3408,6 +3408,9 @@ importers: '@logto/js': specifier: ^2.0.1 version: 2.0.1 + '@logto/language-kit': + specifier: workspace:^1.0.0 + version: link:../toolkit/language-kit '@logto/node': specifier: ^2.0.0 version: 2.0.0