Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions packages/host/tests/unit/operation-lowering-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,9 @@ module('Unit | operation lowering', function (hooks) {
syntax: 'solidified',
},
deterministic: true,
readsActor: true,
},
'a link-typed member becomes a card identity while a scalar one stays the bare builtin',
'a link-typed member becomes a card identity while a scalar one stays the bare builtin, and a program naming the actor says so',
);
});

Expand Down Expand Up @@ -424,8 +425,9 @@ module('Unit | operation lowering', function (hooks) {
postedBy: { $ref: 'actor' },
},
deterministic: true,
readsActor: true,
},
'a linkTo param is a link entry and fill stays data for the coordinator to substitute',
'a linkTo param is a link entry, fill stays data for the coordinator to substitute, and an actor marker inside it is reported the same as one in a program',
);
});

Expand Down Expand Up @@ -528,8 +530,9 @@ module('Unit | operation lowering', function (hooks) {
base: 'read',
output: { source: '{label:actor()}', syntax: 'solidified' },
deterministic: true,
readsActor: true,
},
'a declarative projection and a raw program reach the realm in one form',
'a declarative projection and a raw program reach the realm in one form, and a projection reading the actor is reported like any other stage',
);
});

Expand Down
26 changes: 18 additions & 8 deletions packages/realm-server/tests/card-operations-dispatch-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,9 +480,7 @@ module(basename(import.meta.filename), function () {
test('a file def carries the two writes that work on its bytes', async function (assert) {
// A file's metadata is content-derived and read-only, so what a write on
// one reaches is the bytes: an `update` replaces them wholesale, and an
// `appendLine` adds a line to the end of a text file. Appending a line is
// the one behavior that goes the other way — a line appended to a card's
// stored file leaves behind something that is no longer a card.
// `appendLine` adds a line to the end of a text file.
let file = stub();
for (let name of ['update', 'appendLine']) {
let resolved = await resolveOperation(file.core, FILE, name);
Expand All @@ -492,13 +490,25 @@ module(basename(import.meta.filename), function () {
`a file carries "${name}", undeclared, as a base operation`,
);
}

});
test('appending a line is admitted for any instance, whatever its URL says', async function (assert) {
// A line appended to a card's stored file leaves behind something that is
// no longer a card, so this is the one write a card must not carry — and
// it is refused by `stageAppendLine`, not here. Dispatch classifies an
// instance target by its extension, and the registered-extension table
// does not name every stored file: a `.log`, a `.css`, a `.yml` holds
// bytes and serves them, and each classifies as a card. Refusing here
// would turn those away before the only code that can tell them from a
// card — the executor, which reads whether the path holds a card's
// `.json` and what content type its bytes are.
let card = stub();
let onCard = await refusalFrom(() =>
resolveOperation(card.core, CARD, 'appendLine'),
let resolved = await resolveOperation(card.core, CARD, 'appendLine');
assert.strictEqual(
resolved.base,
'appendLine',
'the behavior resolves, and what it may be applied to is the ' +
"executor's to decide",
);
assert.strictEqual(onCard.code, 'operation-not-allowed');
assert.strictEqual(onCard.status, 405);
});
test('a write is carried out by the coordinator rather than by this dispatch', async function (assert) {
// Every write takes the realm's write lock once for the whole batch it
Expand Down
76 changes: 76 additions & 0 deletions packages/realm-server/tests/prerendering-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,82 @@ module(basename(import.meta.filename), function () {
);
});

test("a module's lowered operations report whether they read the actor", async function (assert) {
// Lowering runs in the prerender host, and this is where the realm
// server sees what it produced: the visit hands back the definitions it
// built, so a member computed at lowering time is asserted on directly
// rather than through indexing and an endpoint. `readsActor` is what
// lets a transport refuse an operation needing an identity before any of
// a batch runs, so it has to survive this channel to be worth anything.
const moduleURL = `${realmURL}report.gts`;
await realmAdapter.write(
'report.gts',
`
import { CardDef, FieldDef, field, contains, containsMany, StringField, Component } from '@cardstack/base/card-api';
import { operation, params, actor } from '@cardstack/base/operations';

export class ReportComment extends FieldDef {
@field body = contains(StringField);
@field postedBy = contains(StringField);
}

export class ExternalReport extends CardDef {
static displayName = "External Report";
@field status = contains(StringField);
@field comments = containsMany(ReportComment);

@operation static addComment = {
base: 'transform',
params: { body: StringField },
append: {
to: 'comments',
value: { body: params('body'), postedBy: actor() },
},
};

@operation static escalate = {
base: 'transform',
set: { status: 'escalated' },
};

static isolated = class extends Component<typeof this> {
<template>{{@model.status}}</template>
}
}
`,
);
realm.__testOnlyClearCaches();

let result = await prerenderer.prerenderModule({
affinityType: 'realm',
affinityValue: realmURL,
realm: realmURL,
url: moduleURL,
auth: auth(),
renderOptions: { clearCache: true },
});

let key = `${trimExecutableExtension(rri(moduleURL))}/ExternalReport`;
let entry = result.response.definitions[key];
if (entry?.type === 'definition') {
let operations = entry.definition.operations ?? {};
let issues = operations.addComment?.issues ?? [];
assert.deepEqual(issues, [], 'the declaration lowers cleanly');
assert.true(
operations.addComment?.readsActor,
'an operation whose program names actor() says so',
);
assert.strictEqual(
operations.escalate?.readsActor,
undefined,
'and one that never reads it carries nothing, which is the absence ' +
'a transport reads as "no identity needed"',
);
} else {
assert.ok(false, "the visit should carry the type's definition");
}
});

test('module prerender reuses pooled page after updates', async function (assert) {
const moduleURL = `${realmURL}person.gts`;

Expand Down
Loading
Loading