|
| 1 | +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. |
| 2 | + |
| 3 | +/** |
| 4 | + * [#25053] `sys_oauth_application.type` declares exactly the application types |
| 5 | + * its one writer produces: `web` and `native`. |
| 6 | + * |
| 7 | + * The column's only writer is `@better-auth/oauth-provider`, whose |
| 8 | + * `applicationType` plugin-auth maps onto `type`. 1.7.3 writes |
| 9 | + * `application_type` `web` or `native` on every creation and update path, and |
| 10 | + * the generic data API cannot write this table (`apiMethods` is `get` / |
| 11 | + * `list`). The column used to declare four options through the string |
| 12 | + * shorthand, which strips hyphens, so it served `web`, `native`, |
| 13 | + * `useragentbased` and `public`. No writer produces the last two (ADR-0049 |
| 14 | + * enforce-or-remove), and the hyphenated `user-agent-based` an earlier |
| 15 | + * provider line wrote was never one of them. |
| 16 | + * |
| 17 | + * What the narrowed declaration does to stored rows is pinned where a real |
| 18 | + * engine runs, in plugin-auth's `oauth-application-type-column.test.ts`. This |
| 19 | + * package cannot import `@objectstack/objectql`, which depends on it. |
| 20 | + */ |
| 21 | + |
| 22 | +import { describe, expect, it } from 'vitest'; |
| 23 | +import { SysOauthApplication } from './index.js'; |
| 24 | +import { enObjects } from '../apps/translations/en.objects.generated.js'; |
| 25 | +import { zhCNObjects } from '../apps/translations/zh-CN.objects.generated.js'; |
| 26 | +import { jaJPObjects } from '../apps/translations/ja-JP.objects.generated.js'; |
| 27 | +import { esESObjects } from '../apps/translations/es-ES.objects.generated.js'; |
| 28 | + |
| 29 | +type Option = { label?: unknown; value?: unknown }; |
| 30 | +type Column = { type?: string; required?: boolean; defaultValue?: unknown; options?: Option[] }; |
| 31 | + |
| 32 | +const fields = SysOauthApplication.fields as Record<string, Column>; |
| 33 | +const column = fields.type; |
| 34 | +const values = (options: unknown[] | undefined): unknown[] => |
| 35 | + (options ?? []).map((o) => (typeof o === 'string' ? o : (o as Option)?.value)); |
| 36 | + |
| 37 | +const LOCALES: readonly (readonly [string, any])[] = [ |
| 38 | + ['en', enObjects], |
| 39 | + ['zh-CN', zhCNObjects], |
| 40 | + ['ja-JP', jaJPObjects], |
| 41 | + ['es-ES', esESObjects], |
| 42 | +]; |
| 43 | + |
| 44 | +describe('[#25053] sys_oauth_application.type — the declared options are what the provider writes', () => { |
| 45 | + it('declares exactly web and native, each value as written', () => { |
| 46 | + expect(column?.type).toBe('select'); |
| 47 | + expect(values(column?.options)).toEqual(['web', 'native']); |
| 48 | + // The string shorthand keeps the written string as the label and derives |
| 49 | + // the value from it; equal pairs say the derivation changed nothing. |
| 50 | + for (const option of column?.options ?? []) { |
| 51 | + expect(option.value, `option ${String(option.label)}`).toBe(option.label); |
| 52 | + } |
| 53 | + }); |
| 54 | + |
| 55 | + it('stays optional, with web as its default', () => { |
| 56 | + // The provider can write null (a client-metadata-document client that |
| 57 | + // names no type): a required column would make that a missing value. |
| 58 | + expect(column?.required).toBe(false); |
| 59 | + expect(column?.defaultValue).toBe('web'); |
| 60 | + }); |
| 61 | + |
| 62 | + it('declares the same values the register form offers', () => { |
| 63 | + const register = (SysOauthApplication.actions ?? []).find((a) => a.name === 'create_oauth_application'); |
| 64 | + const param = ((register?.params ?? []) as { name?: string; options?: unknown[] }[]).find((p) => p.name === 'type'); |
| 65 | + expect(values(param?.options)).toEqual(values(column?.options)); |
| 66 | + }); |
| 67 | + |
| 68 | + it('every shipped locale translates exactly those two options', () => { |
| 69 | + for (const [locale, bundle] of LOCALES) { |
| 70 | + const options = bundle?.sys_oauth_application?.fields?.type?.options; |
| 71 | + expect(Object.keys(options ?? {}), `${locale} type options`).toEqual(['web', 'native']); |
| 72 | + } |
| 73 | + }); |
| 74 | + |
| 75 | + it('keeps the `public` column', () => { |
| 76 | + expect(fields.public?.type).toBe('boolean'); |
| 77 | + }); |
| 78 | +}); |
0 commit comments