diff --git a/packages/realm-server/server.ts b/packages/realm-server/server.ts index 33ef59387e..767fcf79a7 100644 --- a/packages/realm-server/server.ts +++ b/packages/realm-server/server.ts @@ -310,9 +310,14 @@ export class RealmServer { ctxt.type = 'html'; - let cardURL = new URL( + let requestURL = new URL( `${ctxt.protocol}://${ctxt.host}${ctxt.originalUrl}`, ); + let cardURL = requestURL; + let isIndexRequest = requestURL.pathname.endsWith('/'); + if (isIndexRequest) { + cardURL = new URL('index', requestURL); + } let indexHTML = await this.retrieveIndexHTML(); let hasPublicPermissions = await this.hasPublicPermissions(cardURL); diff --git a/packages/realm-server/tests/server-endpoints/helpers.ts b/packages/realm-server/tests/server-endpoints/helpers.ts index 2da55d7706..832ddfbd02 100644 --- a/packages/realm-server/tests/server-endpoints/helpers.ts +++ b/packages/realm-server/tests/server-endpoints/helpers.ts @@ -40,7 +40,16 @@ export type ServerEndpointsTestContext = { startRealmServer: () => Promise; }; -export function setupServerEndpointsTest(hooks: NestedHooks) { +export type ServerEndpointsTestOptions = { + beforeStartRealmServer?: ( + context: ServerEndpointsTestContext, + ) => void | Promise; +}; + +export function setupServerEndpointsTest( + hooks: NestedHooks, + options: ServerEndpointsTestOptions = {}, +) { let context = {} as ServerEndpointsTestContext; let ownerUserId = '@mango:localhost'; @@ -110,6 +119,9 @@ export function setupServerEndpointsTest(hooks: NestedHooks) { context.testRealmDir = join(context.dir.name, 'realm_server_2', 'test'); ensureDirSync(context.testRealmDir); copySync(join(__dirname, '..', 'cards'), context.testRealmDir); + if (options.beforeStartRealmServer) { + await options.beforeStartRealmServer(context); + } await startRealmServer(_dbAdapter, _publisher, _runner); }, afterEach: async () => { diff --git a/packages/realm-server/tests/server-endpoints/index-responses-test.ts b/packages/realm-server/tests/server-endpoints/index-responses-test.ts index 03c9666b10..fb743e3772 100644 --- a/packages/realm-server/tests/server-endpoints/index-responses-test.ts +++ b/packages/realm-server/tests/server-endpoints/index-responses-test.ts @@ -1,14 +1,129 @@ import { module, test } from 'qunit'; -import { basename } from 'path'; +import { join, basename } from 'path'; import { systemInitiatedPriority } from '@cardstack/runtime-common'; import { setupServerEndpointsTest, testRealm2URL } from './helpers'; +import { ensureDirSync, writeFileSync, writeJSONSync } from 'fs-extra'; import '@cardstack/runtime-common/helpers/code-equality-assertion'; module(`server-endpoints/${basename(__filename)}`, function () { module( 'Realm Server Endpoints (not specific to one realm)', function (hooks) { - let context = setupServerEndpointsTest(hooks); + let context = setupServerEndpointsTest(hooks, { + beforeStartRealmServer: async (context) => { + let subdirectoryPath = join(context.testRealmDir, 'subdirectory'); + ensureDirSync(subdirectoryPath); + writeJSONSync(join(subdirectoryPath, 'index.json'), { + data: { + type: 'card', + attributes: { + firstName: 'Subdirectory Index', + }, + meta: { + adoptsFrom: { + module: '../person.gts', + name: 'Person', + }, + }, + }, + }); + + writeFileSync( + join(context.testRealmDir, 'isolated-card.gts'), + ` + import { Component, CardDef } from 'https://cardstack.com/base/card-api'; + + export class IsolatedCard extends CardDef { + static isolated = class Isolated extends Component { + + }; + } + `, + ); + + writeJSONSync(join(context.testRealmDir, 'isolated-test.json'), { + data: { + type: 'card', + attributes: {}, + meta: { + adoptsFrom: { + module: './isolated-card.gts', + name: 'IsolatedCard', + }, + }, + }, + }); + + writeFileSync( + join(context.testRealmDir, 'head-card.gts'), + ` + import { Component, CardDef } from 'https://cardstack.com/base/card-api'; + + export class HeadCard extends CardDef { + static isolated = class Isolated extends Component { + + }; + + static head = class Head extends Component { + + }; + } + `, + ); + + writeJSONSync(join(context.testRealmDir, 'private-index-test.json'), { + data: { + type: 'card', + attributes: {}, + meta: { + adoptsFrom: { + module: './head-card.gts', + name: 'HeadCard', + }, + }, + }, + }); + + writeFileSync( + join(context.testRealmDir, 'scoped-css-card.gts'), + ` + import { Component, CardDef } from 'https://cardstack.com/base/card-api'; + + export class ScopedCssCard extends CardDef { + static isolated = class Isolated extends Component { + + }; + } + `, + ); + + writeJSONSync(join(context.testRealmDir, 'scoped-css-test.json'), { + data: { + type: 'card', + attributes: {}, + meta: { + adoptsFrom: { + module: './scoped-css-card.gts', + name: 'ScopedCssCard', + }, + }, + }, + }); + }, + }); test('startup indexing uses system initiated queue priority', async function (assert) { let [job] = (await context.dbAdapter.execute( @@ -24,14 +139,6 @@ module(`server-endpoints/${basename(__filename)}`, function () { }); test('serves isolated HTML in index responses for card URLs', async function (assert) { - let cardURL = new URL('isolated-test', testRealm2URL).href; - let isolatedHTML = '
Isolated HTML
'; - - await context.dbAdapter.execute( - `INSERT INTO boxel_index_working (url, file_alias, type, realm_version, realm_url, isolated_html) - VALUES ('${cardURL}', '${cardURL}', 'instance', 1, '${testRealm2URL.href}', '${isolatedHTML}')`, - ); - let response = await context.request2 .get('/test/isolated-test') .set('Accept', 'text/html'); @@ -43,17 +150,20 @@ module(`server-endpoints/${basename(__filename)}`, function () { ); }); - test('does not inject head or isolated HTML when realm is not public', async function (assert) { - let cardURL = new URL('private-index-test', testRealm2URL).href; - let headHTML = ''; - let isolatedHTML = - '
Private isolated HTML
'; + test('serves isolated HTML for /subdirectory/index.json at /subdirectory/', async function (assert) { + let response = await context.request2 + .get('/test/subdirectory/') + .set('Accept', 'text/html'); - await context.dbAdapter.execute( - `INSERT INTO boxel_index_working (url, file_alias, type, realm_version, realm_url, head_html, isolated_html) - VALUES ('${cardURL}', '${cardURL}', 'instance', 1, '${testRealm2URL.href}', '${headHTML}', '${isolatedHTML}')`, + assert.strictEqual(response.status, 200, 'serves HTML response'); + + assert.ok( + response.text.includes('Subdirectory Index'), + 'isolated HTML is injected into the HTML response', ); + }); + test('does not inject head or isolated HTML when realm is not public', async function (assert) { await context.dbAdapter.execute( `DELETE FROM realm_user_permissions WHERE realm_url = '${testRealm2URL.href}' AND username = '*'`, ); @@ -74,20 +184,6 @@ module(`server-endpoints/${basename(__filename)}`, function () { }); test('serves scoped CSS in index responses for card URLs', async function (assert) { - let cardURL = new URL('scoped-css-test', testRealm2URL).href; - let scopedCSS = '.layout{display:flex;}'; - let encodedCSS = encodeURIComponent( - Buffer.from(scopedCSS).toString('base64'), - ); - let deps = JSON.stringify([ - `https://cardstack.com/base/card-api.gts.${encodedCSS}.glimmer-scoped.css`, - ]); - - await context.dbAdapter.execute( - `INSERT INTO boxel_index_working (url, file_alias, type, realm_version, realm_url, deps) - VALUES ('${cardURL}', '${cardURL}', 'instance', 1, '${testRealm2URL.href}', '${deps}'::jsonb)`, - ); - let response = await context.request2 .get('/test/scoped-css-test') .set('Accept', 'text/html'); @@ -98,7 +194,7 @@ module(`server-endpoints/${basename(__filename)}`, function () { 'scoped CSS style tag is injected into the HTML response', ); assert.ok( - response.text.includes(scopedCSS), + response.text.includes('--scoped-css-marker: 1'), 'scoped CSS is included in the HTML response', ); });