@actuate-media/cms-core 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/api/collections-discovery.test.d.ts +2 -0
- package/dist/__tests__/api/collections-discovery.test.d.ts.map +1 -0
- package/dist/__tests__/api/collections-discovery.test.js +129 -0
- package/dist/__tests__/api/collections-discovery.test.js.map +1 -0
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +38 -0
- package/dist/api/handlers.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collections-discovery.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/api/collections-discovery.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression tests for the `/api/cms/collections` discovery endpoint
|
|
3
|
+
* shipped in PR F (Phase 7 — MCP-parity stylistic cleanup).
|
|
4
|
+
*
|
|
5
|
+
* Purpose: enumerate the configured collections without relying on the
|
|
6
|
+
* OpenAPI generator, which has historically drifted out of sync with
|
|
7
|
+
* the admin-visible surface. The endpoint is unauthenticated by design
|
|
8
|
+
* — the response contains schema-shape metadata that is already public
|
|
9
|
+
* via `/openapi.json` — but we test that contract here so a future
|
|
10
|
+
* change can't silently widen it.
|
|
11
|
+
*/
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
13
|
+
import { handleActuateAPI } from '../../api/index.js';
|
|
14
|
+
import { setActuateConfig } from '../../config/runtime.js';
|
|
15
|
+
const SECRET = 'test-secret-collections-discovery-pr-f-12345';
|
|
16
|
+
describe('GET /api/cms/collections (PR F discovery endpoint)', () => {
|
|
17
|
+
// Snapshot every env var any test in this file touches so we can
|
|
18
|
+
// restore them in `afterEach`. Mirrors the pattern in health.test.ts —
|
|
19
|
+
// PR #155 review (Bugbot) caught that the prior implementation set
|
|
20
|
+
// CMS_SECRET in beforeEach but never restored it, leaking state into
|
|
21
|
+
// any other test that runs after this file.
|
|
22
|
+
const original = {
|
|
23
|
+
CMS_SECRET: process.env.CMS_SECRET,
|
|
24
|
+
CMS_SESSION_SECRET: process.env.CMS_SESSION_SECRET,
|
|
25
|
+
};
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
process.env.CMS_SECRET = SECRET;
|
|
28
|
+
delete process.env.CMS_SESSION_SECRET;
|
|
29
|
+
});
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
for (const [k, v] of Object.entries(original)) {
|
|
32
|
+
if (v === undefined)
|
|
33
|
+
delete process.env[k];
|
|
34
|
+
else
|
|
35
|
+
process.env[k] = v;
|
|
36
|
+
}
|
|
37
|
+
delete globalThis.__actuateConfig;
|
|
38
|
+
});
|
|
39
|
+
function makeHandler() {
|
|
40
|
+
setActuateConfig({
|
|
41
|
+
secret: SECRET,
|
|
42
|
+
collections: {
|
|
43
|
+
posts: {
|
|
44
|
+
slug: 'posts',
|
|
45
|
+
labels: { singular: 'Post', plural: 'Posts' },
|
|
46
|
+
type: 'post',
|
|
47
|
+
urlPrefix: 'blog',
|
|
48
|
+
fields: {
|
|
49
|
+
title: { type: 'text', required: true },
|
|
50
|
+
body: { type: 'richText' },
|
|
51
|
+
author: { type: 'relationship', relationTo: 'users' },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
pages: {
|
|
55
|
+
slug: 'pages',
|
|
56
|
+
labels: { singular: 'Page', plural: 'Pages' },
|
|
57
|
+
type: 'page',
|
|
58
|
+
fields: {
|
|
59
|
+
title: { type: 'text', required: true },
|
|
60
|
+
slug: { type: 'slug', required: true },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
hidden_internal: {
|
|
64
|
+
slug: 'hidden_internal',
|
|
65
|
+
labels: { singular: 'Internal', plural: 'Internal' },
|
|
66
|
+
fields: { name: { type: 'text' } },
|
|
67
|
+
admin: { hidden: true },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
return handleActuateAPI({ prismaClient: {} });
|
|
72
|
+
}
|
|
73
|
+
it('returns one entry per configured collection with field summaries', async () => {
|
|
74
|
+
const handler = makeHandler();
|
|
75
|
+
const response = await handler(new Request('https://example.com/api/cms/collections', { method: 'GET' }));
|
|
76
|
+
expect(response.status).toBe(200);
|
|
77
|
+
const payload = (await response.json());
|
|
78
|
+
expect(payload.data).toHaveLength(3);
|
|
79
|
+
const bySlug = new Map(payload.data.map((c) => [c.slug, c]));
|
|
80
|
+
const posts = bySlug.get('posts');
|
|
81
|
+
expect(posts.labels).toEqual({ singular: 'Post', plural: 'Posts' });
|
|
82
|
+
expect(posts.type).toBe('post');
|
|
83
|
+
expect(posts.fieldCount).toBe(3);
|
|
84
|
+
expect(posts.fields).toEqual(expect.arrayContaining([
|
|
85
|
+
{ name: 'title', type: 'text', required: true },
|
|
86
|
+
{ name: 'body', type: 'richText', required: false },
|
|
87
|
+
{ name: 'author', type: 'relationship', required: false },
|
|
88
|
+
]));
|
|
89
|
+
const pages = bySlug.get('pages');
|
|
90
|
+
expect(pages.type).toBe('page');
|
|
91
|
+
expect(pages.fieldCount).toBe(2);
|
|
92
|
+
expect(pages.fields.find((f) => f.name === 'title')?.required).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
it('surfaces admin.hidden as `hidden: true` so agents can choose to skip them', async () => {
|
|
95
|
+
// Internal collections should still be discoverable — the agent
|
|
96
|
+
// might legitimately want to read from them — but the flag lets
|
|
97
|
+
// the agent down-rank them in its planning.
|
|
98
|
+
const handler = makeHandler();
|
|
99
|
+
const response = await handler(new Request('https://example.com/api/cms/collections', { method: 'GET' }));
|
|
100
|
+
const payload = (await response.json());
|
|
101
|
+
const hiddenEntry = payload.data.find((c) => c.slug === 'hidden_internal');
|
|
102
|
+
expect(hiddenEntry?.hidden).toBe(true);
|
|
103
|
+
const postsEntry = payload.data.find((c) => c.slug === 'posts');
|
|
104
|
+
expect(postsEntry?.hidden).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
it('returns 500 when no CMS config is registered (deploy misconfig)', async () => {
|
|
107
|
+
// Defensive: route must error loudly when getActuateConfig() is
|
|
108
|
+
// missing instead of returning `{ data: [] }`, which would lie to
|
|
109
|
+
// the MCP fallback chain about the server's actual state.
|
|
110
|
+
const handler = handleActuateAPI({ prismaClient: {} });
|
|
111
|
+
const response = await handler(new Request('https://example.com/api/cms/collections', { method: 'GET' }));
|
|
112
|
+
expect(response.status).toBe(500);
|
|
113
|
+
});
|
|
114
|
+
it('does not leak deeper schema (validators, hooks, access functions)', async () => {
|
|
115
|
+
// The response is unauthenticated — field-level validators or
|
|
116
|
+
// access functions could include source pointers / closure state
|
|
117
|
+
// that mustn't ship in the public surface. Keep the contract
|
|
118
|
+
// tight: only `name`, `type`, `required` per field.
|
|
119
|
+
const handler = makeHandler();
|
|
120
|
+
const response = await handler(new Request('https://example.com/api/cms/collections', { method: 'GET' }));
|
|
121
|
+
const payload = (await response.json());
|
|
122
|
+
for (const collection of payload.data) {
|
|
123
|
+
for (const field of collection.fields) {
|
|
124
|
+
expect(Object.keys(field).sort()).toEqual(['name', 'required', 'type']);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
//# sourceMappingURL=collections-discovery.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collections-discovery.test.js","sourceRoot":"","sources":["../../../src/__tests__/api/collections-discovery.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/api/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAqnB5C,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS9E;AA2ND,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/api/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAqnB5C,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS9E;AA2ND,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAquNzD"}
|
package/dist/api/handlers.js
CHANGED
|
@@ -776,6 +776,44 @@ export function registerCMSRoutes(router) {
|
|
|
776
776
|
headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' },
|
|
777
777
|
});
|
|
778
778
|
});
|
|
779
|
+
// ---------------------------------------------------------------------------
|
|
780
|
+
// Collection discovery (Phase 7 PR F)
|
|
781
|
+
//
|
|
782
|
+
// Lightweight enumeration sourced directly from `getActuateConfig()` so
|
|
783
|
+
// agents and the MCP server's `list_collections` tool don't depend on
|
|
784
|
+
// OpenAPI spec generation succeeding. OpenAPI is still the source of
|
|
785
|
+
// truth for full per-field JSON Schemas; this endpoint trades depth
|
|
786
|
+
// for reliability and surfaces just enough metadata for the agent to
|
|
787
|
+
// plan a follow-up call (create_document, list_documents, etc.).
|
|
788
|
+
//
|
|
789
|
+
// Auth: unauthenticated, like /openapi.json — the response contains
|
|
790
|
+
// only schema-shape metadata that is already public via OpenAPI.
|
|
791
|
+
// ---------------------------------------------------------------------------
|
|
792
|
+
router.get('/collections', async () => {
|
|
793
|
+
const config = getActuateConfig();
|
|
794
|
+
if (!config)
|
|
795
|
+
return errorResponse('CMS not configured', 500);
|
|
796
|
+
const collections = Object.entries(config.collections ?? {}).map(([slug, def]) => {
|
|
797
|
+
const fields = (def?.fields ?? {});
|
|
798
|
+
// Reduce each field to `{ name, type, required }` — enough for the
|
|
799
|
+
// agent to plan a create/update without sending the full validator.
|
|
800
|
+
const fieldSummary = Object.entries(fields).map(([name, fieldDef]) => ({
|
|
801
|
+
name,
|
|
802
|
+
type: typeof fieldDef?.type === 'string' ? fieldDef.type : 'unknown',
|
|
803
|
+
required: fieldDef?.required === true,
|
|
804
|
+
}));
|
|
805
|
+
return {
|
|
806
|
+
slug,
|
|
807
|
+
labels: def?.labels ?? { singular: slug, plural: slug },
|
|
808
|
+
type: def?.type ?? 'page',
|
|
809
|
+
urlPrefix: def?.urlPrefix ?? slug,
|
|
810
|
+
hidden: def?.admin?.hidden === true,
|
|
811
|
+
fieldCount: fieldSummary.length,
|
|
812
|
+
fields: fieldSummary,
|
|
813
|
+
};
|
|
814
|
+
});
|
|
815
|
+
return json({ data: collections });
|
|
816
|
+
});
|
|
779
817
|
router.get('/docs', async () => {
|
|
780
818
|
const html = `<!DOCTYPE html>
|
|
781
819
|
<html>
|