@commonpub/layer 0.83.0 → 0.83.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonpub/layer",
3
- "version": "0.83.0",
3
+ "version": "0.83.2",
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "files": [
@@ -55,16 +55,16 @@
55
55
  "vue-router": "^4.3.0",
56
56
  "zod": "^4.3.6",
57
57
  "@commonpub/auth": "0.8.0",
58
- "@commonpub/config": "0.23.0",
59
58
  "@commonpub/editor": "0.8.0",
59
+ "@commonpub/learning": "0.5.2",
60
60
  "@commonpub/explainer": "0.8.0",
61
61
  "@commonpub/docs": "0.6.3",
62
62
  "@commonpub/protocol": "0.14.0",
63
- "@commonpub/learning": "0.5.2",
64
63
  "@commonpub/schema": "0.46.0",
65
64
  "@commonpub/server": "2.90.0",
65
+ "@commonpub/ui": "0.13.1",
66
66
  "@commonpub/theme-studio": "0.6.1",
67
- "@commonpub/ui": "0.13.1"
67
+ "@commonpub/config": "0.23.0"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@testing-library/jest-dom": "^6.9.1",
@@ -1,127 +0,0 @@
1
- /**
2
- * Auth-gating proving test for `GET /api/content/:id/versions`
3
- * (audit session 204).
4
- *
5
- * Version history (titles, author, timestamps — incl. for unpublished drafts)
6
- * was world-readable for any content id. The fix requires an authenticated
7
- * caller AND that they be the content owner OR hold `content.moderate`.
8
- *
9
- * This drives the REAL route handler against a REAL (PGlite) DB seeded with a
10
- * content item + a version row. The Nitro auth auto-imports are stubbed with
11
- * their REAL semantics (requireAuth throws 401 when anonymous; ownerOrPermission
12
- * returns true iff the caller is the owner or holds the permission), reading a
13
- * per-test `currentUser` — so the handler's actual branch on the gate boolean
14
- * and the DB row's authorId is exercised.
15
- */
16
- import { describe, it, expect, beforeAll } from 'vitest';
17
- import type { H3Event } from 'h3';
18
- import {
19
- createTestDB,
20
- createTestUser,
21
- } from '../../../../../../../packages/server/src/__tests__/helpers/testdb';
22
- import { contentItems, contentVersions } from '@commonpub/schema';
23
- import type { DB } from '../../../../../../../packages/server/src/types';
24
-
25
- interface HttpError extends Error {
26
- statusCode: number;
27
- }
28
- interface TestUser {
29
- id: string;
30
- permissions: Set<string>;
31
- }
32
-
33
- let db: DB;
34
- let contentId: string;
35
- let ownerId: string;
36
- // null => anonymous. permissions => the set the caller holds.
37
- let currentUser: TestUser | null;
38
-
39
- {
40
- const g = globalThis as Record<string, unknown>;
41
- g.defineEventHandler = (fn: unknown): unknown => fn;
42
- g.createError = (opts: { statusCode: number; statusMessage: string }): HttpError => {
43
- const e = new Error(opts.statusMessage) as HttpError;
44
- e.statusCode = opts.statusCode;
45
- return e;
46
- };
47
- // Real semantics: 401 when there is no authenticated user.
48
- g.requireAuth = (_event: H3Event): { id: string } => {
49
- if (!currentUser) {
50
- const e = new Error('Unauthorized') as HttpError;
51
- e.statusCode = 401;
52
- throw e;
53
- }
54
- return { id: currentUser.id };
55
- };
56
- g.useDB = (): DB => db;
57
- // parseParams returns the id under test (route would parse from the path).
58
- g.parseParams = (): { id: string } => ({ id: contentId });
59
- // Real semantics: owner OR permission-holder.
60
- g.ownerOrPermission = (_event: H3Event, resourceOwnerId: string, perm: string): boolean => {
61
- if (!currentUser) return false;
62
- if (currentUser.id === resourceOwnerId) return true;
63
- return currentUser.permissions.has(perm);
64
- };
65
- }
66
-
67
- const handlerMod = await import('../versions.get');
68
- const handler = handlerMod.default as (event: H3Event) => Promise<unknown>;
69
- const fakeEvent = {} as H3Event;
70
-
71
- function statusOf(p: Promise<unknown>): Promise<number | 'no-throw'> {
72
- return p.then(
73
- () => 'no-throw' as const,
74
- (e: HttpError) => e.statusCode,
75
- );
76
- }
77
-
78
- beforeAll(async () => {
79
- db = await createTestDB();
80
- const owner = await createTestUser(db, { username: 'owner' });
81
- ownerId = owner.id;
82
- const [item] = await db
83
- .insert(contentItems)
84
- .values({
85
- authorId: owner.id,
86
- type: 'blog',
87
- title: 'Draft',
88
- slug: 'draft',
89
- status: 'draft',
90
- visibility: 'private',
91
- content: [],
92
- } as never)
93
- .returning();
94
- contentId = (item as { id: string }).id;
95
- await db.insert(contentVersions).values({
96
- contentId,
97
- version: 1,
98
- title: 'Draft v1',
99
- createdById: owner.id,
100
- } as never);
101
- }, 30_000); // PGlite pushSchema is heavy; generous setup timeout under parallel load
102
-
103
- describe('versions.get — author/moderator-only', () => {
104
- it('anonymous caller → 401', async () => {
105
- currentUser = null;
106
- expect(await statusOf(handler(fakeEvent))).toBe(401);
107
- });
108
-
109
- it('authenticated non-owner without content.moderate → 403', async () => {
110
- currentUser = { id: 'some-other-user-id', permissions: new Set() };
111
- expect(await statusOf(handler(fakeEvent))).toBe(403);
112
- });
113
-
114
- it('owner → returns the version list', async () => {
115
- currentUser = { id: ownerId, permissions: new Set() };
116
- const result = (await handler(fakeEvent)) as Array<{ title: string | null }>;
117
- expect(Array.isArray(result)).toBe(true);
118
- expect(result.length).toBe(1);
119
- expect(result[0]?.title).toBe('Draft v1');
120
- });
121
-
122
- it('non-owner WITH content.moderate → returns the version list', async () => {
123
- currentUser = { id: 'mod-user-id', permissions: new Set(['content.moderate']) };
124
- const result = (await handler(fakeEvent)) as unknown[];
125
- expect(result.length).toBe(1);
126
- });
127
- });