Skip to content
49 changes: 28 additions & 21 deletions packages/host/tests/helpers/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,31 +220,13 @@ export class TestRealmAdapter implements RealmAdapter {
}

async exists(path: LocalPath): Promise<boolean> {
let maybeFilename = path.split('/').pop()!;
try {
// a quirk of our test file system's traverse is that it creates
// directories as it goes--so do our best to determine if we are checking for
// a file that exists (because of this behavior directories always exist)
await this.#traverse(
path.split('/'),
maybeFilename.includes('.') ? 'file' : 'directory',
);
this.#traverseExisting(path.split('/'));
return true;
} catch (err: any) {
if (err.name === 'NotFoundError') {
if (['NotFoundError', 'TypeMismatchError'].includes(err.name)) {
return false;
}
if (err.name === 'TypeMismatchError') {
try {
await this.#traverse(path.split('/'), 'file');
return true;
} catch (err: any) {
if (err.name === 'NotFoundError') {
return false;
}
throw err;
}
}
throw err;
}
}
Expand All @@ -253,7 +235,7 @@ export class TestRealmAdapter implements RealmAdapter {
await this.#ready.promise;
let content;
try {
content = this.#traverse(path.split('/'), 'file');
content = this.#traverseExisting(path.split('/'));
} catch (err: any) {
if (['TypeMismatchError', 'NotFoundError'].includes(err.name)) {
return undefined;
Expand Down Expand Up @@ -406,6 +388,31 @@ export class TestRealmAdapter implements RealmAdapter {
return dir;
}

#traverseExisting(
segments: string[],
originalPath = segments.join('/'),
): File | Dir {
let dir: Dir | File = this.#files;
while (segments.length > 0) {
if (dir.kind === 'file') {
let err = new Error(`tried to use file as directory`);
err.name = 'TypeMismatchError';
throw err;
}
let name = segments.shift()!;
if (name === '') {
return dir;
}
if (dir.contents[name] === undefined) {
let err = new Error(`${originalPath} not found`);
err.name = 'NotFoundError';
throw err;
}
dir = dir.contents[name];
}
return dir;
}

createStreamingResponse(
_request: Request,
requestContext: RequestContext,
Expand Down
6 changes: 3 additions & 3 deletions packages/host/tests/helpers/index.gts
Original file line number Diff line number Diff line change
Expand Up @@ -819,12 +819,12 @@ async function setupTestRealm({

// TODO this is the only use of Realm.maybeHandle left--can we get rid of it?
virtualNetwork.mount(realm.maybeHandle);
if (startMatrix) {
await mockMatrixUtils.start();
}
await adapter.ready;
await worker.run();
await realm.start();
if (startMatrix) {
await mockMatrixUtils.start();
}
Comment on lines +825 to +827
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what was the reason for moving this? was it causing test flakiness?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think AI thought it would help. I doubt it matters one way or the other.


let realmServer = getService('realm-server');
if (!realmServer.availableRealmURLs.includes(realmURL)) {
Expand Down
199 changes: 58 additions & 141 deletions packages/realm-server/tests/card-endpoints-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -786,26 +786,6 @@ module(basename(__filename), function () {
);
});
});

module('public readable realm with file', function (hooks) {
setupPermissionedRealmAtURL(hooks, realmURL, {
permissions: {
'*': ['read'],
},
fileSystem: {
'greeting.txt': 'hello',
},
onRealmSetup,
});

test('does not return card JSON for file urls', async function (assert) {
let response = await request
.get('/greeting.txt')
.set('Accept', 'application/vnd.card+json');

assert.strictEqual(response.status, 404, 'HTTP 404 status');
});
});
});

module('card POST request', function (_hooks) {
Expand Down Expand Up @@ -1859,73 +1839,7 @@ module(basename(__filename), function () {
});
});

module('card POST request | file URL', function (_hooks) {
module('public writable realm with file', function (hooks) {
setupPermissionedRealmAtURL(hooks, realmURL, {
permissions: {
'*': ['read', 'write'],
},
fileSystem: {
'greeting.txt': 'hello',
},
onRealmSetup,
});

test('rejects POST to a file URL', async function (assert) {
let response = await request
.post('/greeting.txt')
.send({
data: {
type: 'card',
attributes: {},
meta: {
adoptsFrom: {
module: 'https://cardstack.com/base/card-api',
name: 'CardDef',
},
},
},
})
.set('Accept', 'application/vnd.card+json');

assert.strictEqual(response.status, 405, 'HTTP 405 status');
});
});
});

module('card PATCH request', function (_hooks) {
module('public writable realm with file', function (hooks) {
setupPermissionedRealmAtURL(hooks, realmURL, {
permissions: {
'*': ['read', 'write'],
},
fileSystem: {
'greeting.txt': 'hello',
},
onRealmSetup,
});

test('rejects PATCH to a file URL', async function (assert) {
let response = await request
.patch('/greeting.txt')
.send({
data: {
type: 'card',
attributes: {},
meta: {
adoptsFrom: {
module: 'https://cardstack.com/base/card-api',
name: 'CardDef',
},
},
},
})
.set('Accept', 'application/vnd.card+json');

assert.strictEqual(response.status, 405, 'HTTP 405 status');
});
});

module('public writable realm', function (hooks) {
setupPermissionedRealmAtURL(hooks, realmURL, {
permissions: {
Expand Down Expand Up @@ -3416,61 +3330,7 @@ module(basename(__filename), function () {
});
});

module('card PUT request | file URL', function (_hooks) {
module('public writable realm with file', function (hooks) {
setupPermissionedRealmAtURL(hooks, realmURL, {
permissions: {
'*': ['read', 'write'],
},
fileSystem: {
'greeting.txt': 'hello',
},
onRealmSetup,
});

test('rejects PUT to a file URL', async function (assert) {
let response = await request
.put('/greeting.txt')
.send({
data: {
type: 'card',
attributes: {},
meta: {
adoptsFrom: {
module: 'https://cardstack.com/base/card-api',
name: 'CardDef',
},
},
},
})
.set('Accept', 'application/vnd.card+json');

assert.strictEqual(response.status, 405, 'HTTP 405 status');
});
});
});

module('card DELETE request', function (_hooks) {
module('public writable realm with file', function (hooks) {
setupPermissionedRealmAtURL(hooks, realmURL, {
permissions: {
'*': ['read', 'write'],
},
fileSystem: {
'greeting.txt': 'hello',
},
onRealmSetup,
});

test('rejects DELETE to a file URL', async function (assert) {
let response = await request
.delete('/greeting.txt')
.set('Accept', 'application/vnd.card+json');

assert.strictEqual(response.status, 405, 'HTTP 405 status');
});
});

module('public writable realm', function (hooks) {
setupPermissionedRealmAtURL(hooks, realmURL, {
permissions: {
Expand Down Expand Up @@ -3543,7 +3403,7 @@ module(basename(__filename), function () {
assert.false(existsSync(cardFile), 'card json does not exist');
});

test('removes file meta when card is deleted', async function (assert) {
test('removes card JSON file meta when card is deleted', async function (assert) {
// confirm meta.resourceCreatedAt exists prior to deletion
let initial = await request
.get('/person-1')
Expand Down Expand Up @@ -3622,6 +3482,63 @@ module(basename(__filename), function () {
});
});
});

module('file URLs', function (hooks) {
setupPermissionedRealmAtURL(hooks, realmURL, {
permissions: {
'*': ['read', 'write'],
},
fileSystem: {
'greeting.txt': 'hello',
},
onRealmSetup,
});

test('rejects HTTP requests to file URLs', async function (assert) {
let response;
response = await request
.get('/greeting.txt')
.set('Accept', 'application/vnd.card+json');

assert.strictEqual(
response.status,
415,
'rejects GET for a file URL with 415 status',
);

response = await request
.patch('/greeting.txt')
.send({
data: {
type: 'card',
attributes: {},
meta: {
adoptsFrom: {
module: 'https://cardstack.com/base/card-api',
name: 'CardDef',
},
},
},
})
.set('Accept', 'application/vnd.card+json');

assert.strictEqual(
response.status,
415,
'rejects PATCH to a file URL with 415 status',
);

response = await request
.delete('/greeting.txt')
.set('Accept', 'application/vnd.card+json');

assert.strictEqual(
response.status,
415,
'rejects DELETE to a file URL with 415 status',
);
});
});
});

module('Query-backed relationships runtime resolver', function (hooks) {
Expand Down
15 changes: 15 additions & 0 deletions packages/runtime-common/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,21 @@ export function methodNotAllowed(
);
}

export function unsupportedMediaType(
request: Request,
requestContext: RequestContext,
): Response {
return responseWithError(
new CardError(
`request for ${request.url} can't be fulfilled for accept header ${request.headers.get('accept')}`,
{
status: 415,
},
),
requestContext,
);
}

export function notFound(
request: Request,
requestContext: RequestContext,
Expand Down
Loading