Skip to content
Open
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
42 changes: 42 additions & 0 deletions packages/core/src/__tests__/app.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { describe, expect, it } from 'vitest';
import { createSonicJSApp } from '../app';
import { createRedirectPlugin } from '../plugins/redirect-management';

describe('createSonicJSApp — plugin route ordering', () => {
it('GET /api/redirects reaches the redirect plugin, not the /:collection catch-all', async () => {
const app = createSonicJSApp({
plugins: {
register: [createRedirectPlugin() as any],
},
});

// Simulate a request with a minimal env (DB stub, development mode for open API access)
const res = await app.request(
'/api/redirects',
{
headers: { 'x-test-role': 'admin' },
},
{
DB: {
prepare: () => ({
bind: (..._args: unknown[]) => ({
all: async () => ({ results: [] }),
first: async () => null,
run: async () => ({ success: true }),
}),
all: async () => ({ results: [] }),
}),
batch: async () => [],
},
ENVIRONMENT: 'development',
},
);

const body = await res.json();

// The redirect plugin should handle this request.
// If the catch-all shadows it, we get { error: "Collection not found" } with 404.
expect(res.status).not.toBe(404);
expect(body).not.toHaveProperty('error', 'Collection not found');
});
});
42 changes: 22 additions & 20 deletions packages/core/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -628,29 +628,13 @@ export function createSonicJSApp(config: SonicJSConfig = {}): SonicJSApp {
app.route('/api/media', apiMediaRoutes)
app.route('/api/system', apiSystemRoutes)
app.route('/api/documents', apiDocumentsRoutes)
app.route('/api', apiRoutes)
app.route('/admin/documents', adminDocumentsRoutes)


// Forms (admin builder, public rendering, API submission). Same as above —
// routes[] was replaced with register(app) in the definePlugin port.
registerPluginRoutes(app, [formsPlugin as any], { source: 'core' })

app.route('/admin/api', adminApiRoutes)
app.route('/admin/collections', adminCollectionsRoutes)
app.route('/admin/settings', adminSettingsRoutes)
app.route('/admin/api-reference', adminApiReferenceRoutes)
app.route('/admin/database-tools', createDatabaseToolsAdminRoutes())
app.route('/admin/content', adminContentRoutes)
app.route('/admin/media', adminMediaRoutes)
// Security audit middleware - logs auth events (login, register, logout)
app.use('/auth/*', securityAuditMiddleware())

// ── Plugin routes (before the /admin catch-all) ───────────────────────────
// ── Plugin routes (before the /api catch-all) ─────────────────────────────
// All plugin route mounting flows through registerPluginRoutes() (see
// plugins/mount.ts), which mounts each plugin's declarative routes[] and/or
// synchronous register(app) hook. These MUST be mounted before the bare
// `/admin` catch-all so plugin-owned `/admin/<x>` pages are not shadowed.
// synchronous register(app) hook. These MUST be mounted before both the bare
// `/api` catch-all (whose /:collection param shadows /api/* plugin routes) and
// the `/admin` catch-all so plugin-owned pages are not shadowed.
//
// `disableAll` turns off every plugin — core AND user — for a bare core app.
if (!config.plugins?.disableAll) {
Expand All @@ -667,6 +651,24 @@ export function createSonicJSApp(config: SonicJSConfig = {}): SonicJSApp {
}
}

app.route('/api', apiRoutes)
app.route('/admin/documents', adminDocumentsRoutes)


// Forms (admin builder, public rendering, API submission). Same as above —
// routes[] was replaced with register(app) in the definePlugin port.
registerPluginRoutes(app, [formsPlugin as any], { source: 'core' })

app.route('/admin/api', adminApiRoutes)
app.route('/admin/collections', adminCollectionsRoutes)
app.route('/admin/settings', adminSettingsRoutes)
app.route('/admin/api-reference', adminApiReferenceRoutes)
app.route('/admin/database-tools', createDatabaseToolsAdminRoutes())
app.route('/admin/content', adminContentRoutes)
app.route('/admin/media', adminMediaRoutes)
// Security audit middleware - logs auth events (login, register, logout)
app.use('/auth/*', securityAuditMiddleware())

// Public event tracking API — POST /api/events (open), GET /api/events (admin)
app.route('/api/events', eventsApiRoutes)

Expand Down