Skip to content

Commit f847836

Browse files
authored
Merge pull request #1211 from HarperFast/kris/integration-test-custom-resources
test(integration): custom jsResource patterns (#1190)
2 parents 7845a09 + 2a89b01 commit f847836

4 files changed

Lines changed: 452 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
graphqlSchema:
2+
files: '*.graphql'
3+
jsResource:
4+
files: resources.js
5+
rest: true
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// WorkItem: async write-then-patch pattern (CDI RT enqueueing + AI inference result attachment)
2+
export class WorkItem extends tables.WorkItem {
3+
async post(body, _ctx) {
4+
const id = Math.random().toString(36).slice(2);
5+
// Use static class method to create a new record by explicit id
6+
await tables.WorkItem.put({ id, state: 'pending', payload: JSON.stringify(body) });
7+
return { id, state: 'pending' };
8+
}
9+
async patch(body, _ctx) {
10+
// this is the loaded record instance; doesExist() tells us if it was found
11+
if (!this.doesExist()) return new Response(null, { status: 404 });
12+
const current = await this.get();
13+
// Full update via single-arg super.put (legacy: update(record, true) + save)
14+
await super.put({ ...current, state: 'completed', result: body.result, resultAt: new Date() });
15+
return { state: 'completed' };
16+
}
17+
}
18+
19+
// RedirectRule: routing decision with audit trail (Walmart USGM pattern)
20+
export class RedirectRule extends tables.RedirectRule {
21+
async post(body, _ctx) {
22+
// chain detection: check if redirectUrl matches any existing matchUrl
23+
const existing = [];
24+
for await (const r of tables.RedirectRule.search({
25+
conditions: [{ attribute: 'matchUrl', value: body.redirectUrl }],
26+
})) {
27+
existing.push(r);
28+
}
29+
if (existing.length > 0) {
30+
const context = this.getContext();
31+
if (context?.response) context.response.status = 409;
32+
return { error: 'Chain redirect detected' };
33+
}
34+
const id = Math.random().toString(36).slice(2);
35+
const user = this.getContext()?.user;
36+
const record = { id, ...body, createdBy: user?.username || 'anonymous' };
37+
// Write the redirect rule and audit record using static methods
38+
await tables.RedirectRule.put(record);
39+
await tables.RedirectChange.put({
40+
id: Math.random().toString(36).slice(2),
41+
redirectId: id,
42+
operation: 'create',
43+
previousState: null,
44+
});
45+
return record;
46+
}
47+
}
48+
49+
// Block external mutations on RedirectChange (audit log is immutable from outside)
50+
export class RedirectChange extends tables.RedirectChange {
51+
post() {
52+
return new Response(null, { status: 405 });
53+
}
54+
put() {
55+
return new Response(null, { status: 405 });
56+
}
57+
patch() {
58+
return new Response(null, { status: 405 });
59+
}
60+
delete() {
61+
return new Response(null, { status: 405 });
62+
}
63+
}
64+
65+
// RoutingDecision: POST-only routing lookup endpoint (Walmart USGM)
66+
export class RoutingDecision extends Resource {
67+
static loadAsInstance = false;
68+
async post(query, body) {
69+
const { path } = body;
70+
if (!path) return {};
71+
const now = new Date();
72+
const rules = [];
73+
for await (const r of tables.RedirectRule.search({ conditions: [{ attribute: 'matchUrl', value: path }] })) {
74+
rules.push(r);
75+
}
76+
const rule = rules.find((r) => {
77+
if (r.startTime && new Date(r.startTime) > now) return false;
78+
if (r.endTime && new Date(r.endTime) < now) return false;
79+
return true;
80+
});
81+
if (rule) return { shouldRedirect: true, status: rule.statusCode || 302, location: rule.redirectUrl };
82+
return {};
83+
}
84+
}
85+
86+
// AbuseCounter: atomic counter with 403 threshold (Ford PasswordResetAbuse pattern)
87+
export class AbuseCounter extends tables.AbuseCounter {
88+
async put(_body, _ctx) {
89+
// this is the loaded record instance; get current count from the stored record
90+
const current = await this.get();
91+
const id = this.getId();
92+
const newCount = ((current && current.count) || 0) + 1;
93+
if (newCount > 5) {
94+
const context = this.getContext();
95+
if (context?.response) context.response.status = 403;
96+
return { error: 'Too many attempts' };
97+
}
98+
// Full update via single-arg super.put (legacy: update(record, true) + save)
99+
await super.put({ id, count: newCount });
100+
return { count: newCount };
101+
}
102+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
type WorkItem @table @export {
2+
id: ID @primaryKey
3+
state: String @indexed
4+
payload: String
5+
result: String
6+
resultAt: Date @updatedTime
7+
}
8+
9+
type RedirectRule @table @export @sealed {
10+
id: ID @primaryKey
11+
matchUrl: String @indexed
12+
redirectUrl: String @indexed
13+
statusCode: Int
14+
startTime: Date
15+
endTime: Date
16+
createdBy: String
17+
}
18+
19+
type RedirectChange @table @export {
20+
id: ID @primaryKey
21+
redirectId: String @indexed
22+
operation: String
23+
previousState: String
24+
createdAt: Date @createdTime
25+
}
26+
27+
type AbuseCounter @table(expiration: 10) @export {
28+
id: ID @primaryKey
29+
count: Int
30+
}

0 commit comments

Comments
 (0)