Skip to content

Commit fd46c37

Browse files
authored
Merge pull request #5095 from cardstack/cs-11344-error-with-blackjack-game
Resolve cross-realm file links instead of erroring
2 parents e784a8d + dbf9ba7 commit fd46c37

2 files changed

Lines changed: 137 additions & 16 deletions

File tree

packages/realm-server/tests/card-endpoints-test.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4363,6 +4363,120 @@ module(basename(__filename), function () {
43634363
});
43644364
});
43654365

4366+
module('cross-realm file links', function (hooks) {
4367+
// A card instantiated from the catalog (e.g. a blackjack game) keeps a
4368+
// linksTo(FileDef) reference to an image file that lives in the catalog
4369+
// realm. When the card is served from a different realm, loadLinks resolves
4370+
// that reference via the cross-realm fetch path. Regression for CS-11344:
4371+
// that path used to assume every cross-realm link target was a card and
4372+
// threw "instance ... is not a card document" on a file-meta target,
4373+
// surfacing as an HTTP 500.
4374+
const providerRealmURL = 'http://127.0.0.1:5531/test/';
4375+
const consumerRealmURL = 'http://127.0.0.1:5532/test/';
4376+
let consumerRequest: RealmRequest;
4377+
4378+
setupPermissionedRealmsCached(hooks, {
4379+
realms: [
4380+
{
4381+
realmURL: providerRealmURL,
4382+
permissions: {
4383+
'*': ['read', 'write', 'realm-owner'],
4384+
'@node-test_realm:localhost': ['read', 'realm-owner'],
4385+
},
4386+
fileSystem: {
4387+
'instructions.md': '# Cross-realm instructions',
4388+
},
4389+
},
4390+
{
4391+
realmURL: consumerRealmURL,
4392+
permissions: {
4393+
'*': ['read', 'write', 'realm-owner'],
4394+
'@node-test_realm:localhost': ['read', 'realm-owner'],
4395+
},
4396+
fileSystem: {
4397+
'skill-card.gts': `
4398+
import { CardDef, field, contains, linksTo } from "https://cardstack.com/base/card-api";
4399+
import StringField from "https://cardstack.com/base/string";
4400+
import { MarkdownDef } from "https://cardstack.com/base/markdown-file-def";
4401+
4402+
export class SkillCard extends CardDef {
4403+
@field cardTitle = contains(StringField);
4404+
@field instructionsSource = linksTo(MarkdownDef);
4405+
}
4406+
`,
4407+
'skill.json': {
4408+
data: {
4409+
attributes: {
4410+
cardTitle: 'Cross-realm skill',
4411+
},
4412+
relationships: {
4413+
instructionsSource: {
4414+
links: {
4415+
self: `${providerRealmURL}instructions.md`,
4416+
},
4417+
},
4418+
},
4419+
meta: {
4420+
adoptsFrom: {
4421+
module: rri('./skill-card'),
4422+
name: 'SkillCard',
4423+
},
4424+
},
4425+
},
4426+
},
4427+
},
4428+
},
4429+
],
4430+
onRealmSetup({ realms }) {
4431+
let latestRealms = realms.slice(-2);
4432+
consumerRequest = withRealmPath(
4433+
supertest(latestRealms[1].realmHttpServer),
4434+
new URL(consumerRealmURL),
4435+
);
4436+
},
4437+
});
4438+
4439+
hooks.afterEach(() => {
4440+
resetCatalogRealms();
4441+
});
4442+
4443+
test('serves a card linking to a file in another realm', async function (assert) {
4444+
let response = await consumerRequest
4445+
.get('/skill')
4446+
.set('Accept', 'application/vnd.card+json');
4447+
4448+
assert.strictEqual(
4449+
response.status,
4450+
200,
4451+
`HTTP 200 status: ${response.text}`,
4452+
);
4453+
4454+
let doc = response.body as LooseSingleCardDocument;
4455+
let relationship = doc.data.relationships
4456+
?.instructionsSource as Relationship;
4457+
assert.deepEqual(
4458+
relationship?.data,
4459+
{
4460+
type: 'file-meta',
4461+
id: `${providerRealmURL}instructions.md`,
4462+
},
4463+
'cross-realm file relationship references the file-meta target',
4464+
);
4465+
4466+
let included = doc.included ?? [];
4467+
let linkedFile = included.find(
4468+
(resource) => resource.id === `${providerRealmURL}instructions.md`,
4469+
);
4470+
assert.ok(linkedFile, 'includes the cross-realm file-meta resource');
4471+
assert.strictEqual(
4472+
linkedFile?.type,
4473+
'file-meta',
4474+
'cross-realm linked resource is a file-meta resource',
4475+
);
4476+
assert.strictEqual(linkedFile?.attributes?.name, 'instructions.md');
4477+
});
4478+
});
4479+
43664480
module('Query-backed relationships runtime resolver', function (hooks) {
43674481
const providerRealmURL = 'http://127.0.0.1:5521/test/';
43684482
const consumerRealmURL = 'http://127.0.0.1:5522/test/';

packages/runtime-common/realm-index-query-engine.ts

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import {
5555
} from './code-ref';
5656
import {
5757
isSingleCardDocument,
58+
isSingleFileMetaDocument,
5859
type SingleCardDocument,
5960
type LinkableCollectionDocument,
6061
isLinkableCollectionDocument,
@@ -1178,7 +1179,7 @@ export class RealmIndexQueryEngine {
11781179
urls: string[],
11791180
invocationId: string,
11801181
layerIndex: number,
1181-
): Promise<Map<string, CardResource<Saved>>> {
1182+
): Promise<Map<string, CardResource<Saved> | FileMetaResource>> {
11821183
let entries = await Promise.all(
11831184
urls.map(async (url) => {
11841185
let response: Response;
@@ -1201,20 +1202,24 @@ export class RealmIndexQueryEngine {
12011202
throw await CardError.fromFetchResponse(url, response);
12021203
}
12031204
let json = await response.json();
1204-
if (!isSingleCardDocument(json)) {
1205-
throw new Error(
1206-
`instance ${url} is not a card document. it is: ${JSON.stringify(
1207-
json,
1208-
null,
1209-
2,
1210-
)}`,
1211-
);
1205+
// Cross-realm links can target either a card or a file (e.g. a card
1206+
// instantiated from a catalog still links to the catalog's image
1207+
// file). Both kinds are valid linked resources; only an unrecognized
1208+
// payload is an error.
1209+
if (isSingleCardDocument(json) || isSingleFileMetaDocument(json)) {
1210+
let linkResource: CardResource<Saved> | FileMetaResource = {
1211+
...json.data,
1212+
...{ links: { self: json.data.id } },
1213+
};
1214+
return [url, linkResource] as const;
12121215
}
1213-
let linkResource: CardResource<Saved> = {
1214-
...json.data,
1215-
...{ links: { self: json.data.id } },
1216-
};
1217-
return [url, linkResource] as const;
1216+
throw new Error(
1217+
`linked resource ${url} is not a card or file document. it is: ${JSON.stringify(
1218+
json,
1219+
null,
1220+
2,
1221+
)}`,
1222+
);
12181223
}),
12191224
);
12201225
return new Map(entries);
@@ -1635,7 +1640,7 @@ export class RealmIndexQueryEngine {
16351640
let batchStart = Date.now();
16361641
let instanceMap: Map<string, InstanceOrError>;
16371642
let fileMap: Map<string, IndexedFile>;
1638-
let crossRealmMap: Map<string, CardResource<Saved>>;
1643+
let crossRealmMap: Map<string, CardResource<Saved> | FileMetaResource>;
16391644
try {
16401645
[instanceMap, fileMap, crossRealmMap] = await Promise.all([
16411646
inRealmCardURLs.size > 0
@@ -1656,7 +1661,9 @@ export class RealmIndexQueryEngine {
16561661
invocationId,
16571662
currentLayerIndex,
16581663
)
1659-
: Promise.resolve(new Map<string, CardResource<Saved>>()),
1664+
: Promise.resolve(
1665+
new Map<string, CardResource<Saved> | FileMetaResource>(),
1666+
),
16601667
]);
16611668
} catch (err: unknown) {
16621669
let message =

0 commit comments

Comments
 (0)