From f65fa7f6514822001c123aec7d0574e6ae91d968 Mon Sep 17 00:00:00 2001 From: Adam Daley Date: Thu, 5 Nov 2026 17:02:09 +0000 Subject: [PATCH 1/4] Document v2 extension badges and full requirements --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fd14278a..8672b403 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,12 @@ Your [account profile](https://extensions.fossbilling.org/account/profile) (disp The [`FOSSBilling/api`](https://github.com/FOSSBilling/api) repo serves badges for use in READMEs and project pages: ```text -https://api.fossbilling.net/extensions/v1/Example/badges/version -https://api.fossbilling.net/extensions/v1/Example/badges/min_fossbilling_version -https://api.fossbilling.net/extensions/v1/Example/badges/license +https://api.fossbilling.net/extensions/v2/extensions/branding/badges/version +https://api.fossbilling.net/extensions/v2/extensions/branding/badges/fossbilling +https://api.fossbilling.net/extensions/v2/extensions/branding/badges/license ``` -Colors can be customized with `?color=`. +The `fossbilling` badge displays the latest release's complete version requirement, including upper bounds, exclusions, and compound ranges. Replace `branding` with your extension ID. Colors can be customized with `?color=green`. Existing v1 badge URLs remain available for older embeds, but their minimum-version badge does not express the full compatibility range. ## Contributing From 3665d49f0ad68bce5473a0de8d9fca36b93b6f1b Mon Sep 17 00:00:00 2001 From: Adam Daley Date: Thu, 5 Nov 2026 17:40:36 +0000 Subject: [PATCH 2/4] Tighten badge documentation wording --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8672b403..3d9240f0 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ https://api.fossbilling.net/extensions/v2/extensions/branding/badges/fossbilling https://api.fossbilling.net/extensions/v2/extensions/branding/badges/license ``` -The `fossbilling` badge displays the latest release's complete version requirement, including upper bounds, exclusions, and compound ranges. Replace `branding` with your extension ID. Colors can be customized with `?color=green`. Existing v1 badge URLs remain available for older embeds, but their minimum-version badge does not express the full compatibility range. +Replace `branding` with your extension ID. The `fossbilling` badge shows the full requirement, such as `>=0.8.9 <1.0 || ^2.0`. Use `?color=green` to change the color. Existing v1 URLs still work, but minimum-version badges cannot show the full range. ## Contributing From e02df5019f0b75c6ae3a1008b56f0a8a0e1b394f Mon Sep 17 00:00:00 2001 From: Adam Daley Date: Thu, 5 Nov 2026 18:02:49 +0000 Subject: [PATCH 3/4] Use the v2 namespace throughout the extension site --- README.md | 8 ++--- openapi/extensions-v2.json | 2 +- scripts/update-extensions-openapi.ts | 2 +- src/lib/api/client.ts | 4 +-- .../api/generated/extensions-v2/client.gen.ts | 2 +- .../api/generated/extensions-v2/types.gen.ts | 2 +- test/extensions-api.test.ts | 30 ++++++++----------- 7 files changed, 22 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 3d9240f0..9246aca5 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ Your [account profile](https://extensions.fossbilling.org/account/profile) (disp The [`FOSSBilling/api`](https://github.com/FOSSBilling/api) repo serves badges for use in READMEs and project pages: ```text -https://api.fossbilling.net/extensions/v2/extensions/branding/badges/version -https://api.fossbilling.net/extensions/v2/extensions/branding/badges/fossbilling -https://api.fossbilling.net/extensions/v2/extensions/branding/badges/license +https://api.fossbilling.net/v2/extensions/branding/badges/version +https://api.fossbilling.net/v2/extensions/branding/badges/fossbilling +https://api.fossbilling.net/v2/extensions/branding/badges/license ``` Replace `branding` with your extension ID. The `fossbilling` badge shows the full requirement, such as `>=0.8.9 <1.0 || ^2.0`. Use `?color=green` to change the color. Existing v1 URLs still work, but minimum-version badges cannot show the full range. @@ -46,7 +46,7 @@ For broader discussion, join the FOSSBilling community on [Discord](https://foss ## Local Development -Astro site on Cloudflare Workers. Extension data comes from the [`FOSSBilling/api`](https://github.com/FOSSBilling/api) repo's `/extensions/v2` service via the generated client in `src/lib/api/generated/extensions-v2`. Sign-in is delegated to `auth.fossbilling.net` via OAuth2/OIDC. +Astro site on Cloudflare Workers. Extension data comes from the [`FOSSBilling/api`](https://github.com/FOSSBilling/api) repo's `/v2` service via the generated client in `src/lib/api/generated/extensions-v2`. Sign-in is delegated to `auth.fossbilling.net` via OAuth2/OIDC. Install dependencies: diff --git a/openapi/extensions-v2.json b/openapi/extensions-v2.json index 0586ae6c..2225af7a 100644 --- a/openapi/extensions-v2.json +++ b/openapi/extensions-v2.json @@ -7,7 +7,7 @@ }, "servers": [ { - "url": "/extensions/v2" + "url": "/v2" } ], "components": { diff --git a/scripts/update-extensions-openapi.ts b/scripts/update-extensions-openapi.ts index 0bbb6af0..ea5b896d 100644 --- a/scripts/update-extensions-openapi.ts +++ b/scripts/update-extensions-openapi.ts @@ -2,7 +2,7 @@ import { mkdir, writeFile } from 'node:fs/promises'; const sourceUrl = process.env.EXTENSIONS_API_OPENAPI_URL ?? - 'https://api.fossbilling.net/extensions/v2/docs/openapi.json'; + 'https://api.fossbilling.net/v2/docs/openapi.json'; const response = await fetch(sourceUrl); if (!response.ok) { diff --git a/src/lib/api/client.ts b/src/lib/api/client.ts index 07fa4d19..6636ad5c 100644 --- a/src/lib/api/client.ts +++ b/src/lib/api/client.ts @@ -246,7 +246,7 @@ function createApiTransport(env: ApplicationEnv, subject?: string): Client { const baseUrl = env.extensionsApi.baseUrl.replace(/\/$/, ''); return createClient({ - baseUrl: `${baseUrl}/extensions/v2`, + baseUrl: `${baseUrl}/v2`, fetch: env.extensionsApi.fetch, ...(subject ? { @@ -500,7 +500,7 @@ export function createApiClient(env: ApplicationEnv, subject: string) { ); const baseUrl = env.extensionsApi.baseUrl.replace(/\/$/, ''); const response = await env.extensionsApi.fetch( - new Request(`${baseUrl}/extensions/v2/users/me/identity`, { + new Request(`${baseUrl}/v2/users/me/identity`, { method: 'PUT', headers: { 'Content-Type': 'application/json', diff --git a/src/lib/api/generated/extensions-v2/client.gen.ts b/src/lib/api/generated/extensions-v2/client.gen.ts index b6044d03..07e8d285 100644 --- a/src/lib/api/generated/extensions-v2/client.gen.ts +++ b/src/lib/api/generated/extensions-v2/client.gen.ts @@ -22,5 +22,5 @@ export type CreateClientConfig = ( ) => Config & T>; export const client: Client = createClient( - createConfig({ baseUrl: '/extensions/v2' }), + createConfig({ baseUrl: '/v2' }), ); diff --git a/src/lib/api/generated/extensions-v2/types.gen.ts b/src/lib/api/generated/extensions-v2/types.gen.ts index cdabaefb..69f60129 100644 --- a/src/lib/api/generated/extensions-v2/types.gen.ts +++ b/src/lib/api/generated/extensions-v2/types.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts export type ClientOptions = { - baseUrl: `${string}://${string}/extensions/v2` | (string & {}); + baseUrl: `${string}://${string}/v2` | (string & {}); }; export type UnifiedExtensionListResponse = { diff --git a/test/extensions-api.test.ts b/test/extensions-api.test.ts index a18bdf35..0835905e 100644 --- a/test/extensions-api.test.ts +++ b/test/extensions-api.test.ts @@ -221,7 +221,7 @@ describe('generated Extensions v2 façade', () => { const response = await listExtensions(publicEnv); const url = requestUrl(fetchMock); - expect(url.pathname).toBe('/extensions/v2/extensions'); + expect(url.pathname).toBe('/v2/extensions'); expect(url.searchParams.get('limit')).toBe('50'); expect(url.searchParams.has('cursor')).toBe(false); expect(response.result).toEqual(firstPage.result); @@ -299,7 +299,7 @@ describe('generated Extensions v2 façade', () => { const response = await getExtensionById(publicEnv, 'full-extension'); expect(requestFrom(fetchMock).url).toBe( - 'https://api.example.test/extensions/v2/extensions/full-extension', + 'https://api.example.test/v2/extensions/full-extension', ); expect(response.readme).toContain('README content'); expect(response.releases).toHaveLength(2); @@ -345,7 +345,7 @@ describe('generated Extensions v2 façade', () => { expect(requestFrom(fetchMock, 1).headers.get('authorization')).toBe( 'Bearer token-two', ); - expect(requestUrl(fetchMock, 0).pathname).toBe('/extensions/v2/extensions'); + expect(requestUrl(fetchMock, 0).pathname).toBe('/v2/extensions'); expect(requestUrl(fetchMock, 0).searchParams.get('scope')).toBe('mine'); expect(requestUrl(fetchMock, 0).searchParams.get('limit')).toBe('100'); expect(requestUrl(fetchMock, 0).searchParams.get('cursor')).toBe( @@ -397,7 +397,7 @@ describe('generated Extensions v2 façade', () => { ).createExtension(payload); const request = requestFrom(fetchMock); - expect(new URL(request.url).pathname).toBe('/extensions/v2/extensions'); + expect(new URL(request.url).pathname).toBe('/v2/extensions'); expect(request.method).toBe('POST'); expect(request.headers.get('content-type')).toBe('application/json'); expect(await request.json()).toEqual(payload); @@ -444,9 +444,7 @@ describe('generated Extensions v2 façade', () => { ).updateExtension('body-extension', payload); const request = requestFrom(fetchMock); - expect(new URL(request.url).pathname).toBe( - '/extensions/v2/extensions/body-extension', - ); + expect(new URL(request.url).pathname).toBe('/v2/extensions/body-extension'); expect(request.method).toBe('PUT'); expect(await request.json()).toEqual(payload); expect(result.revision_id).toBe('revision-2'); @@ -466,9 +464,7 @@ describe('generated Extensions v2 façade', () => { ).withdrawExtension('body-extension'); const request = requestFrom(fetchMock); - expect(new URL(request.url).pathname).toBe( - '/extensions/v2/extensions/body-extension', - ); + expect(new URL(request.url).pathname).toBe('/v2/extensions/body-extension'); expect(request.method).toBe('DELETE'); expect(result).toEqual({ id: 'body-extension', deleted: true }); }); @@ -490,7 +486,7 @@ describe('generated Extensions v2 façade', () => { ); const approveRequest = requestFrom(approveFetch); expect(new URL(approveRequest.url).pathname).toBe( - '/extensions/v2/extensions/body-extension/revisions/r-1/approve', + '/v2/extensions/body-extension/revisions/r-1/approve', ); expect(approveRequest.body).toBeNull(); @@ -507,7 +503,7 @@ describe('generated Extensions v2 façade', () => { ); const rejectRequest = requestFrom(rejectFetch); expect(new URL(rejectRequest.url).pathname).toBe( - '/extensions/v2/extensions/body-extension/revisions/r-2/reject', + '/v2/extensions/body-extension/revisions/r-2/reject', ); expect(await rejectRequest.json()).toEqual({ review_note: 'needs work' }); }); @@ -542,7 +538,7 @@ describe('generated Extensions v2 façade', () => { const request = requestFrom(fetchMock); expect(requestUrl(fetchMock).pathname).toBe( - '/extensions/v2/extensions/live-ext/moderator-correct', + '/v2/extensions/live-ext/moderator-correct', ); expect(request.method).toBe('POST'); // No notify opt-out travels: corrections never email the author (api#251). @@ -628,7 +624,7 @@ describe('generated Extensions v2 façade', () => { next_cursor: 'next-page', has_more: true, }); - expect(requestUrl(fetchMock).pathname).toBe('/extensions/v2/revisions'); + expect(requestUrl(fetchMock).pathname).toBe('/v2/revisions'); expect(requestUrl(fetchMock).searchParams.get('status')).toBe('approved'); expect(requestUrl(fetchMock).searchParams.get('cursor')).toBe( 'queue-cursor', @@ -724,7 +720,7 @@ describe('generated Extensions v2 façade', () => { ); expect(fetchMock).toHaveBeenCalledTimes(1); expect(requestUrl(fetchMock).pathname).toBe( - '/extensions/v2/extensions/extension-1/revisions/revision-1', + '/v2/extensions/extension-1/revisions/revision-1', ); expect(requestFrom(fetchMock).headers.get('Authorization')).toBe( 'Bearer test-token', @@ -826,9 +822,7 @@ describe('identity sync', () => { expect(fetchMock).toHaveBeenCalledTimes(1); const request = requestFrom(fetchMock); expect(request.method).toBe('PUT'); - expect(request.url).toBe( - 'https://api.example.test/extensions/v2/users/me/identity', - ); + expect(request.url).toBe('https://api.example.test/v2/users/me/identity'); expect(request.headers.get('content-type')).toBe('application/json'); expect(request.headers.get('authorization')).toBe('Bearer sync-token'); // The shared user-authentication assertion must not mint here: the API From d07fbaab2a00d8c23dd0d1ad77670ff8ba3996e6 Mon Sep 17 00:00:00 2001 From: Adam Daley Date: Thu, 5 Nov 2026 18:16:57 +0000 Subject: [PATCH 4/4] Revert "Use the v2 namespace throughout the extension site" This reverts commit e02df5019f0b75c6ae3a1008b56f0a8a0e1b394f. --- README.md | 8 ++--- openapi/extensions-v2.json | 2 +- scripts/update-extensions-openapi.ts | 2 +- src/lib/api/client.ts | 4 +-- .../api/generated/extensions-v2/client.gen.ts | 2 +- .../api/generated/extensions-v2/types.gen.ts | 2 +- test/extensions-api.test.ts | 30 +++++++++++-------- 7 files changed, 28 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 9246aca5..3d9240f0 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ Your [account profile](https://extensions.fossbilling.org/account/profile) (disp The [`FOSSBilling/api`](https://github.com/FOSSBilling/api) repo serves badges for use in READMEs and project pages: ```text -https://api.fossbilling.net/v2/extensions/branding/badges/version -https://api.fossbilling.net/v2/extensions/branding/badges/fossbilling -https://api.fossbilling.net/v2/extensions/branding/badges/license +https://api.fossbilling.net/extensions/v2/extensions/branding/badges/version +https://api.fossbilling.net/extensions/v2/extensions/branding/badges/fossbilling +https://api.fossbilling.net/extensions/v2/extensions/branding/badges/license ``` Replace `branding` with your extension ID. The `fossbilling` badge shows the full requirement, such as `>=0.8.9 <1.0 || ^2.0`. Use `?color=green` to change the color. Existing v1 URLs still work, but minimum-version badges cannot show the full range. @@ -46,7 +46,7 @@ For broader discussion, join the FOSSBilling community on [Discord](https://foss ## Local Development -Astro site on Cloudflare Workers. Extension data comes from the [`FOSSBilling/api`](https://github.com/FOSSBilling/api) repo's `/v2` service via the generated client in `src/lib/api/generated/extensions-v2`. Sign-in is delegated to `auth.fossbilling.net` via OAuth2/OIDC. +Astro site on Cloudflare Workers. Extension data comes from the [`FOSSBilling/api`](https://github.com/FOSSBilling/api) repo's `/extensions/v2` service via the generated client in `src/lib/api/generated/extensions-v2`. Sign-in is delegated to `auth.fossbilling.net` via OAuth2/OIDC. Install dependencies: diff --git a/openapi/extensions-v2.json b/openapi/extensions-v2.json index 2225af7a..0586ae6c 100644 --- a/openapi/extensions-v2.json +++ b/openapi/extensions-v2.json @@ -7,7 +7,7 @@ }, "servers": [ { - "url": "/v2" + "url": "/extensions/v2" } ], "components": { diff --git a/scripts/update-extensions-openapi.ts b/scripts/update-extensions-openapi.ts index ea5b896d..0bbb6af0 100644 --- a/scripts/update-extensions-openapi.ts +++ b/scripts/update-extensions-openapi.ts @@ -2,7 +2,7 @@ import { mkdir, writeFile } from 'node:fs/promises'; const sourceUrl = process.env.EXTENSIONS_API_OPENAPI_URL ?? - 'https://api.fossbilling.net/v2/docs/openapi.json'; + 'https://api.fossbilling.net/extensions/v2/docs/openapi.json'; const response = await fetch(sourceUrl); if (!response.ok) { diff --git a/src/lib/api/client.ts b/src/lib/api/client.ts index 6636ad5c..07fa4d19 100644 --- a/src/lib/api/client.ts +++ b/src/lib/api/client.ts @@ -246,7 +246,7 @@ function createApiTransport(env: ApplicationEnv, subject?: string): Client { const baseUrl = env.extensionsApi.baseUrl.replace(/\/$/, ''); return createClient({ - baseUrl: `${baseUrl}/v2`, + baseUrl: `${baseUrl}/extensions/v2`, fetch: env.extensionsApi.fetch, ...(subject ? { @@ -500,7 +500,7 @@ export function createApiClient(env: ApplicationEnv, subject: string) { ); const baseUrl = env.extensionsApi.baseUrl.replace(/\/$/, ''); const response = await env.extensionsApi.fetch( - new Request(`${baseUrl}/v2/users/me/identity`, { + new Request(`${baseUrl}/extensions/v2/users/me/identity`, { method: 'PUT', headers: { 'Content-Type': 'application/json', diff --git a/src/lib/api/generated/extensions-v2/client.gen.ts b/src/lib/api/generated/extensions-v2/client.gen.ts index 07e8d285..b6044d03 100644 --- a/src/lib/api/generated/extensions-v2/client.gen.ts +++ b/src/lib/api/generated/extensions-v2/client.gen.ts @@ -22,5 +22,5 @@ export type CreateClientConfig = ( ) => Config & T>; export const client: Client = createClient( - createConfig({ baseUrl: '/v2' }), + createConfig({ baseUrl: '/extensions/v2' }), ); diff --git a/src/lib/api/generated/extensions-v2/types.gen.ts b/src/lib/api/generated/extensions-v2/types.gen.ts index 69f60129..cdabaefb 100644 --- a/src/lib/api/generated/extensions-v2/types.gen.ts +++ b/src/lib/api/generated/extensions-v2/types.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts export type ClientOptions = { - baseUrl: `${string}://${string}/v2` | (string & {}); + baseUrl: `${string}://${string}/extensions/v2` | (string & {}); }; export type UnifiedExtensionListResponse = { diff --git a/test/extensions-api.test.ts b/test/extensions-api.test.ts index 0835905e..a18bdf35 100644 --- a/test/extensions-api.test.ts +++ b/test/extensions-api.test.ts @@ -221,7 +221,7 @@ describe('generated Extensions v2 façade', () => { const response = await listExtensions(publicEnv); const url = requestUrl(fetchMock); - expect(url.pathname).toBe('/v2/extensions'); + expect(url.pathname).toBe('/extensions/v2/extensions'); expect(url.searchParams.get('limit')).toBe('50'); expect(url.searchParams.has('cursor')).toBe(false); expect(response.result).toEqual(firstPage.result); @@ -299,7 +299,7 @@ describe('generated Extensions v2 façade', () => { const response = await getExtensionById(publicEnv, 'full-extension'); expect(requestFrom(fetchMock).url).toBe( - 'https://api.example.test/v2/extensions/full-extension', + 'https://api.example.test/extensions/v2/extensions/full-extension', ); expect(response.readme).toContain('README content'); expect(response.releases).toHaveLength(2); @@ -345,7 +345,7 @@ describe('generated Extensions v2 façade', () => { expect(requestFrom(fetchMock, 1).headers.get('authorization')).toBe( 'Bearer token-two', ); - expect(requestUrl(fetchMock, 0).pathname).toBe('/v2/extensions'); + expect(requestUrl(fetchMock, 0).pathname).toBe('/extensions/v2/extensions'); expect(requestUrl(fetchMock, 0).searchParams.get('scope')).toBe('mine'); expect(requestUrl(fetchMock, 0).searchParams.get('limit')).toBe('100'); expect(requestUrl(fetchMock, 0).searchParams.get('cursor')).toBe( @@ -397,7 +397,7 @@ describe('generated Extensions v2 façade', () => { ).createExtension(payload); const request = requestFrom(fetchMock); - expect(new URL(request.url).pathname).toBe('/v2/extensions'); + expect(new URL(request.url).pathname).toBe('/extensions/v2/extensions'); expect(request.method).toBe('POST'); expect(request.headers.get('content-type')).toBe('application/json'); expect(await request.json()).toEqual(payload); @@ -444,7 +444,9 @@ describe('generated Extensions v2 façade', () => { ).updateExtension('body-extension', payload); const request = requestFrom(fetchMock); - expect(new URL(request.url).pathname).toBe('/v2/extensions/body-extension'); + expect(new URL(request.url).pathname).toBe( + '/extensions/v2/extensions/body-extension', + ); expect(request.method).toBe('PUT'); expect(await request.json()).toEqual(payload); expect(result.revision_id).toBe('revision-2'); @@ -464,7 +466,9 @@ describe('generated Extensions v2 façade', () => { ).withdrawExtension('body-extension'); const request = requestFrom(fetchMock); - expect(new URL(request.url).pathname).toBe('/v2/extensions/body-extension'); + expect(new URL(request.url).pathname).toBe( + '/extensions/v2/extensions/body-extension', + ); expect(request.method).toBe('DELETE'); expect(result).toEqual({ id: 'body-extension', deleted: true }); }); @@ -486,7 +490,7 @@ describe('generated Extensions v2 façade', () => { ); const approveRequest = requestFrom(approveFetch); expect(new URL(approveRequest.url).pathname).toBe( - '/v2/extensions/body-extension/revisions/r-1/approve', + '/extensions/v2/extensions/body-extension/revisions/r-1/approve', ); expect(approveRequest.body).toBeNull(); @@ -503,7 +507,7 @@ describe('generated Extensions v2 façade', () => { ); const rejectRequest = requestFrom(rejectFetch); expect(new URL(rejectRequest.url).pathname).toBe( - '/v2/extensions/body-extension/revisions/r-2/reject', + '/extensions/v2/extensions/body-extension/revisions/r-2/reject', ); expect(await rejectRequest.json()).toEqual({ review_note: 'needs work' }); }); @@ -538,7 +542,7 @@ describe('generated Extensions v2 façade', () => { const request = requestFrom(fetchMock); expect(requestUrl(fetchMock).pathname).toBe( - '/v2/extensions/live-ext/moderator-correct', + '/extensions/v2/extensions/live-ext/moderator-correct', ); expect(request.method).toBe('POST'); // No notify opt-out travels: corrections never email the author (api#251). @@ -624,7 +628,7 @@ describe('generated Extensions v2 façade', () => { next_cursor: 'next-page', has_more: true, }); - expect(requestUrl(fetchMock).pathname).toBe('/v2/revisions'); + expect(requestUrl(fetchMock).pathname).toBe('/extensions/v2/revisions'); expect(requestUrl(fetchMock).searchParams.get('status')).toBe('approved'); expect(requestUrl(fetchMock).searchParams.get('cursor')).toBe( 'queue-cursor', @@ -720,7 +724,7 @@ describe('generated Extensions v2 façade', () => { ); expect(fetchMock).toHaveBeenCalledTimes(1); expect(requestUrl(fetchMock).pathname).toBe( - '/v2/extensions/extension-1/revisions/revision-1', + '/extensions/v2/extensions/extension-1/revisions/revision-1', ); expect(requestFrom(fetchMock).headers.get('Authorization')).toBe( 'Bearer test-token', @@ -822,7 +826,9 @@ describe('identity sync', () => { expect(fetchMock).toHaveBeenCalledTimes(1); const request = requestFrom(fetchMock); expect(request.method).toBe('PUT'); - expect(request.url).toBe('https://api.example.test/v2/users/me/identity'); + expect(request.url).toBe( + 'https://api.example.test/extensions/v2/users/me/identity', + ); expect(request.headers.get('content-type')).toBe('application/json'); expect(request.headers.get('authorization')).toBe('Bearer sync-token'); // The shared user-authentication assertion must not mint here: the API