-
Notifications
You must be signed in to change notification settings - Fork 12
Add more setup for RealmResourceIdentifier migration #4425
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7df6350
a27b123
e04d6f5
cbb7115
cf90f62
67fa32f
f03b021
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import { | |
| resolveCardReference, | ||
| resolveRRI, | ||
| RealmPaths, | ||
| VirtualNetwork, | ||
| } from '@cardstack/runtime-common'; | ||
| import type { | ||
| SingleCardDocument, | ||
|
|
@@ -618,4 +619,133 @@ module(basename(__filename), function () { | |
| }); | ||
| }); | ||
| }); | ||
|
|
||
| module('VirtualNetwork.addRealmMapping', function (hooks) { | ||
| let vn: VirtualNetwork; | ||
| let prefix = '@test/realm/'; | ||
| let target = 'http://localhost:9000/realm/'; | ||
|
|
||
| hooks.beforeEach(function () { | ||
| vn = new VirtualNetwork(); | ||
| vn.addRealmMapping(prefix, target); | ||
| }); | ||
|
|
||
| hooks.afterEach(function () { | ||
| unregisterCardReferencePrefix(prefix); | ||
| }); | ||
|
|
||
| test('populates importMap so resolveImport works', function (assert) { | ||
| let result = vn.resolveImport('@test/realm/card-api'); | ||
| assert.strictEqual(result, 'http://localhost:9000/realm/card-api'); | ||
| }); | ||
|
|
||
| test('populates global prefixMappings so resolveCardReference works', function (assert) { | ||
| let result = resolveCardReference('@test/realm/Foo', undefined); | ||
| assert.strictEqual(result, 'http://localhost:9000/realm/Foo'); | ||
| }); | ||
|
|
||
| test('normalizes trailing slashes', function (assert) { | ||
| unregisterCardReferencePrefix(prefix); | ||
| let vn2 = new VirtualNetwork(); | ||
| // No trailing slashes | ||
| vn2.addRealmMapping('@test/other', 'http://localhost:9000/other'); | ||
| let result = vn2.resolveImport('@test/other/card'); | ||
| assert.strictEqual(result, 'http://localhost:9000/other/card'); | ||
| unregisterCardReferencePrefix('@test/other/'); | ||
| }); | ||
|
|
||
| test('overwrites cleanly when called twice with same prefix', function (assert) { | ||
| let newTarget = 'http://localhost:8000/realm/'; | ||
| vn.addRealmMapping(prefix, newTarget); | ||
| let result = vn.resolveImport('@test/realm/card-api'); | ||
| assert.strictEqual(result, 'http://localhost:8000/realm/card-api'); | ||
| }); | ||
|
|
||
| test('knownRealms returns registered realm identifiers', function (assert) { | ||
| let realms = vn.knownRealms(); | ||
| assert.true( | ||
| realms.includes('@test/realm/' as any), | ||
| 'contains the registered realm', | ||
| ); | ||
| }); | ||
|
|
||
| test('knownRealms reflects multiple registrations', function (assert) { | ||
| vn.addRealmMapping('@test/other/', 'http://localhost:9000/other/'); | ||
| let realms = vn.knownRealms(); | ||
| assert.strictEqual(realms.length, 2); | ||
| assert.true(realms.includes('@test/realm/' as any)); | ||
| assert.true(realms.includes('@test/other/' as any)); | ||
| unregisterCardReferencePrefix('@test/other/'); | ||
|
Comment on lines
+675
to
+678
|
||
| }); | ||
| }); | ||
|
|
||
| module('VirtualNetwork.fetch with RRI', function (hooks) { | ||
| let vn: VirtualNetwork; | ||
| let prefix = '@test/fetch-realm/'; | ||
| let target = 'http://localhost:9000/fetch-realm/'; | ||
|
|
||
| hooks.beforeEach(function () { | ||
| vn = new VirtualNetwork(); | ||
| vn.addRealmMapping(prefix, target); | ||
| }); | ||
|
|
||
| hooks.afterEach(function () { | ||
| unregisterCardReferencePrefix(prefix); | ||
| }); | ||
|
|
||
| test('resolves scoped RRI to real URL and fetches', async function (assert) { | ||
| let interceptedUrl: string | undefined; | ||
| vn.mount(async (req: Request) => { | ||
| interceptedUrl = req.url; | ||
| return new Response('ok', { status: 200 }); | ||
| }); | ||
|
|
||
| let response = await vn.fetch('@test/fetch-realm/card-api'); | ||
| assert.strictEqual(response.status, 200); | ||
| assert.strictEqual( | ||
| interceptedUrl, | ||
| 'http://localhost:9000/fetch-realm/card-api', | ||
| ); | ||
| }); | ||
|
|
||
| test('passes through normal URLs unchanged', async function (assert) { | ||
| let interceptedUrl: string | undefined; | ||
| vn.mount(async (req: Request) => { | ||
| interceptedUrl = req.url; | ||
| return new Response('ok', { status: 200 }); | ||
| }); | ||
|
|
||
| await vn.fetch('http://localhost:9000/fetch-realm/card-api'); | ||
| assert.strictEqual( | ||
| interceptedUrl, | ||
| 'http://localhost:9000/fetch-realm/card-api', | ||
| ); | ||
| }); | ||
|
|
||
| test('passes RequestInit through when fetching with RRI', async function (assert) { | ||
| let interceptedMethod: string | undefined; | ||
| vn.mount(async (req: Request) => { | ||
| interceptedMethod = req.method; | ||
| return new Response('ok', { status: 200 }); | ||
| }); | ||
|
|
||
| await vn.fetch('@test/fetch-realm/card-api', { method: 'POST' }); | ||
| assert.strictEqual(interceptedMethod, 'POST'); | ||
| }); | ||
|
|
||
| test('@cardstack/base/card-api resolves through full fetch chain', async function (assert) { | ||
| let baseVN = new VirtualNetwork(); | ||
| baseVN.addRealmMapping('@cardstack/base/', 'http://localhost:4201/base/'); | ||
| let interceptedUrl: string | undefined; | ||
| baseVN.mount(async (req: Request) => { | ||
| interceptedUrl = req.url; | ||
| return new Response('ok', { status: 200 }); | ||
| }); | ||
|
|
||
| let response = await baseVN.fetch('@cardstack/base/card-api'); | ||
| assert.strictEqual(response.status, 200); | ||
| assert.strictEqual(interceptedUrl, 'http://localhost:4201/base/card-api'); | ||
| unregisterCardReferencePrefix('@cardstack/base/'); | ||
| }); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,9 @@ | ||
| import { RealmPaths } from './paths'; | ||
| import { RealmPaths, ensureTrailingSlash } from './paths'; | ||
| import { baseRealm, isNode } from './index'; | ||
| import { | ||
| registerCardReferencePrefix, | ||
| type RealmIdentifier, | ||
| } from './card-reference-resolver'; | ||
| import type { ModuleDescriptor } from './package-shim-handler'; | ||
| import { | ||
| PackageShimHandler, | ||
|
|
@@ -19,6 +23,7 @@ export class VirtualNetwork { | |
| private handlers: Handler[] = []; | ||
| private urlMappings: [string, string][] = []; | ||
| private importMap: Map<string, (rest: string) => string> = new Map(); | ||
| private realmMappings = new Map<string, string>(); | ||
|
|
||
| constructor(nativeFetch = createEnvironmentAwareFetch()) { | ||
| this.nativeFetch = nativeFetch; | ||
|
|
@@ -66,6 +71,24 @@ export class VirtualNetwork { | |
| this.importMap.set(prefix, handler); | ||
| } | ||
|
|
||
| addRealmMapping(realmIdentifier: string, targetURL: string): void { | ||
| let normalizedId = ensureTrailingSlash(realmIdentifier); | ||
| let normalizedTarget = ensureTrailingSlash(targetURL); | ||
| this.realmMappings.set(normalizedId, normalizedTarget); | ||
|
|
||
| // Backward compat bridge: populate both existing registration systems | ||
| // so that resolveImport and resolveCardReference continue to work | ||
| this.addImportMap( | ||
| normalizedId, | ||
| (rest) => new URL(rest, normalizedTarget).href, | ||
| ); | ||
| registerCardReferencePrefix(normalizedId, normalizedTarget); | ||
| } | ||
|
Comment on lines
+74
to
+86
|
||
|
|
||
| knownRealms(): RealmIdentifier[] { | ||
| return [...this.realmMappings.keys()] as RealmIdentifier[]; | ||
| } | ||
|
|
||
| private nativeFetch: typeof globalThis.fetch; | ||
|
|
||
| private resolveURLMapping( | ||
|
|
@@ -118,6 +141,15 @@ export class VirtualNetwork { | |
| urlOrRequest: string | URL | Request, | ||
| init?: RequestInit, | ||
| ) => { | ||
| // Resolve RRI strings to real URLs before creating the Request, | ||
| // since new Request('@cardstack/base/...') would throw (not a valid URL). | ||
| if (typeof urlOrRequest === 'string') { | ||
| let resolved = this.resolveRRIToURL(urlOrRequest); | ||
| if (resolved) { | ||
| urlOrRequest = resolved; | ||
| } | ||
| } | ||
|
|
||
| let request = | ||
| urlOrRequest instanceof Request | ||
| ? urlOrRequest | ||
|
|
@@ -135,6 +167,15 @@ export class VirtualNetwork { | |
| return response; | ||
| }; | ||
|
|
||
| private resolveRRIToURL(rri: string): string | undefined { | ||
| for (let [prefix, target] of this.realmMappings) { | ||
| if (rri.startsWith(prefix)) { | ||
| return new URL(rri.slice(prefix.length), target).href; | ||
| } | ||
| } | ||
| return undefined; | ||
| } | ||
|
|
||
| private async runFetch(request: Request, init?: RequestInit) { | ||
| let handlers: FetcherMiddlewareHandler[] = this.handlers.map((h) => { | ||
| return async (request, next) => { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These assertions use
as anyto satisfy theRealmIdentifier[]type, which disables type checking in the test. SinceRealmIdentifieris already imported in this file, prefer casting the string literal toRealmIdentifier(or otherwise comparing asstring) so the test stays type-safe.