@varshylinc/onboarding-consent-engine 0.1.0 → 0.1.1

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.
Files changed (66) hide show
  1. package/dist/client/components/ConsentBlock.d.ts +13 -0
  2. package/dist/client/components/ConsentBlock.d.ts.map +1 -0
  3. package/dist/client/components/ConsentBlock.js +14 -0
  4. package/dist/client/components/ConsentBlock.js.map +1 -0
  5. package/dist/client/components/ConsentCheckbox.d.ts +11 -0
  6. package/dist/client/components/ConsentCheckbox.d.ts.map +1 -0
  7. package/dist/client/components/ConsentCheckbox.js +5 -0
  8. package/dist/client/components/ConsentCheckbox.js.map +1 -0
  9. package/dist/client/components/ConsentUpdateModal.d.ts +12 -0
  10. package/dist/client/components/ConsentUpdateModal.d.ts.map +1 -0
  11. package/dist/client/components/ConsentUpdateModal.js +9 -0
  12. package/dist/client/components/ConsentUpdateModal.js.map +1 -0
  13. package/dist/client/components/EmptyState.d.ts +8 -0
  14. package/dist/client/components/EmptyState.d.ts.map +1 -0
  15. package/dist/client/components/EmptyState.js +5 -0
  16. package/dist/client/components/EmptyState.js.map +1 -0
  17. package/dist/client/components/WelcomeScreen.d.ts +15 -0
  18. package/dist/client/components/WelcomeScreen.d.ts.map +1 -0
  19. package/dist/client/components/WelcomeScreen.js +7 -0
  20. package/dist/client/components/WelcomeScreen.js.map +1 -0
  21. package/{src/client/components/index.ts → dist/client/components/index.d.ts} +1 -0
  22. package/dist/client/components/index.d.ts.map +1 -0
  23. package/dist/client/components/index.js +6 -0
  24. package/dist/client/components/index.js.map +1 -0
  25. package/dist/client/index.d.ts +4 -0
  26. package/dist/client/index.d.ts.map +1 -0
  27. package/dist/client/index.js +2 -0
  28. package/dist/client/index.js.map +1 -0
  29. package/package.json +11 -6
  30. package/.eslintrc.cjs +0 -18
  31. package/CHANGELOG.md +0 -11
  32. package/MODULE.md +0 -130
  33. package/src/client/components/ConsentBlock.tsx +0 -73
  34. package/src/client/components/ConsentCheckbox.tsx +0 -53
  35. package/src/client/components/ConsentUpdateModal.tsx +0 -65
  36. package/src/client/components/EmptyState.tsx +0 -38
  37. package/src/client/components/WelcomeScreen.tsx +0 -64
  38. package/src/client/index.ts +0 -19
  39. package/src/index.ts +0 -20
  40. package/src/server/index.ts +0 -143
  41. package/src/server/lib/getAuditTrail.ts +0 -20
  42. package/src/server/lib/getCurrentConsents.ts +0 -25
  43. package/src/server/lib/getUserLatestConsents.ts +0 -27
  44. package/src/server/lib/hasUserConsented.ts +0 -18
  45. package/src/server/lib/index.ts +0 -7
  46. package/src/server/lib/needsConsentUpdate.ts +0 -28
  47. package/src/server/lib/recordConsent.ts +0 -13
  48. package/src/server/lib/recordSignupConsents.ts +0 -32
  49. package/src/server/migrations/0001_create_oce_schema_migrations.sql +0 -7
  50. package/src/server/migrations/0002_create_oce_consent_definitions.sql +0 -12
  51. package/src/server/migrations/0003_create_oce_user_consents.sql +0 -14
  52. package/src/server/migrations/0004_create_oce_consent_version_log.sql +0 -12
  53. package/src/server/templates/applyProductName.ts +0 -10
  54. package/src/server/templates/index.ts +0 -3
  55. package/src/server/templates/standardConsents.ts +0 -37
  56. package/src/shared/types.ts +0 -85
  57. package/tests/integration/integration.test.ts +0 -162
  58. package/tests/setup/global-setup.ts +0 -16
  59. package/tests/unit/applyProductName.test.ts +0 -20
  60. package/tests/unit/getAuditTrail.test.ts +0 -33
  61. package/tests/unit/hasUserConsented.test.ts +0 -24
  62. package/tests/unit/needsConsentUpdate.test.ts +0 -24
  63. package/tests/unit/recordConsent.test.ts +0 -41
  64. package/tsconfig.client.json +0 -15
  65. package/tsconfig.json +0 -19
  66. package/vitest.config.ts +0 -9
@@ -1,162 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
- import { Pool } from 'pg';
3
- import {
4
- runMigrations,
5
- seedStandardConsents,
6
- createConsentModule,
7
- } from '../../src/server/index.js';
8
-
9
- const describeWithDb = process.env.DATABASE_URL ? describe : describe.skip;
10
-
11
- let pool: Pool;
12
- let consent: ReturnType<typeof createConsentModule>;
13
-
14
- describeWithDb('@varshylinc/onboarding-consent-engine — integration', () => {
15
- beforeAll(async () => {
16
- pool = new Pool({ connectionString: process.env.DATABASE_URL });
17
- // Migrations already applied by global-setup; seed again is idempotent
18
- await seedStandardConsents(pool, 'TestProduct');
19
- consent = createConsentModule({ pool });
20
- });
21
-
22
- afterAll(async () => {
23
- await pool.end();
24
- });
25
-
26
- it('runMigrations is idempotent — second call skips all', async () => {
27
- const { applied, skipped } = await runMigrations(pool);
28
- expect(applied).toHaveLength(0);
29
- expect(skipped).toHaveLength(4);
30
- });
31
-
32
- it('oce_consent_definitions has 4 standard rows after seed', async () => {
33
- const result = await pool.query('SELECT COUNT(*) FROM oce_consent_definitions');
34
- expect(Number(result.rows[0].count)).toBeGreaterThanOrEqual(4);
35
- });
36
-
37
- it('recordSignupConsents inserts records for all provided keys', async () => {
38
- const userId = `test-user-${Date.now()}`;
39
- const records = await consent.recordSignupConsents({
40
- userId,
41
- consents: [
42
- { key: 'terms_of_service', granted: true },
43
- { key: 'privacy_policy', granted: true },
44
- { key: 'marketing_emails', granted: false },
45
- ],
46
- ipAddress: '127.0.0.1',
47
- userAgent: 'vitest',
48
- });
49
- expect(records).toHaveLength(3);
50
- expect(records.every((r) => r.user_id === userId)).toBe(true);
51
- });
52
-
53
- it('hasUserConsented returns true for granted key', async () => {
54
- const userId = `huc-${Date.now()}`;
55
- await consent.recordSignupConsents({
56
- userId,
57
- consents: [{ key: 'privacy_policy', granted: true }],
58
- });
59
- expect(await consent.hasUserConsented(userId, 'privacy_policy')).toBe(true);
60
- });
61
-
62
- it('hasUserConsented returns false for un-consented key', async () => {
63
- const userId = `huc2-${Date.now()}`;
64
- expect(await consent.hasUserConsented(userId, 'terms_of_service')).toBe(false);
65
- });
66
-
67
- it('needsConsentUpdate returns required definitions for new user', async () => {
68
- const userId = `ncu-${Date.now()}`;
69
- const pending = await consent.needsConsentUpdate(userId);
70
- // terms_of_service and privacy_policy are required
71
- expect(pending.length).toBeGreaterThanOrEqual(2);
72
- expect(pending.every((d) => d.required)).toBe(true);
73
- });
74
-
75
- it('needsConsentUpdate returns empty after user consents to all required', async () => {
76
- const userId = `ncu2-${Date.now()}`;
77
- await consent.recordSignupConsents({
78
- userId,
79
- consents: [
80
- { key: 'terms_of_service', granted: true },
81
- { key: 'privacy_policy', granted: true },
82
- ],
83
- });
84
- const pending = await consent.needsConsentUpdate(userId);
85
- expect(pending).toHaveLength(0);
86
- });
87
-
88
- it('getAuditTrail returns all events newest-first', async () => {
89
- const userId = `audit-${Date.now()}`;
90
- await consent.recordSignupConsents({
91
- userId,
92
- consents: [
93
- { key: 'terms_of_service', granted: true },
94
- { key: 'marketing_emails', granted: false },
95
- ],
96
- });
97
- const trail = await consent.getAuditTrail(userId);
98
- expect(trail.length).toBeGreaterThanOrEqual(2);
99
- expect(trail[0].user_id).toBe(userId);
100
- });
101
-
102
- it('getCurrentConsents returns latest state per key', async () => {
103
- const userId = `cc-${Date.now()}`;
104
- await consent.recordSignupConsents({
105
- userId,
106
- consents: [
107
- { key: 'terms_of_service', granted: true },
108
- { key: 'privacy_policy', granted: true },
109
- ],
110
- });
111
- const statuses = await consent.getCurrentConsents(userId);
112
- expect(statuses.length).toBeGreaterThanOrEqual(2);
113
- expect(statuses.every((s) => s.granted === true)).toBe(true);
114
- });
115
-
116
- it('getUserLatestConsents returns a map keyed by user_id', async () => {
117
- const uid1 = `bulk-a-${Date.now()}`;
118
- const uid2 = `bulk-b-${Date.now()}`;
119
- await Promise.all([
120
- consent.recordSignupConsents({
121
- userId: uid1,
122
- consents: [{ key: 'terms_of_service', granted: true }],
123
- }),
124
- consent.recordSignupConsents({
125
- userId: uid2,
126
- consents: [{ key: 'privacy_policy', granted: true }],
127
- }),
128
- ]);
129
- const map = await consent.getUserLatestConsents([uid1, uid2]);
130
- expect(map.has(uid1)).toBe(true);
131
- expect(map.has(uid2)).toBe(true);
132
- expect(map.get(uid1)!.some((s) => s.key === 'terms_of_service')).toBe(true);
133
- });
134
-
135
- it('recordSignupConsents throws for unknown consent key', async () => {
136
- await expect(
137
- consent.recordSignupConsents({
138
- userId: 'err-user',
139
- consents: [{ key: 'nonexistent_key', granted: true }],
140
- }),
141
- ).rejects.toThrow('Unknown consent key: nonexistent_key');
142
- });
143
-
144
- it('onConsentRecorded adapter hook is called', async () => {
145
- const calls: Array<{ userId: string; key: string; granted: boolean }> = [];
146
- const tracked = createConsentModule({
147
- pool,
148
- adapter: {
149
- onConsentRecorded(userId, key, granted) {
150
- calls.push({ userId, key, granted });
151
- },
152
- },
153
- });
154
- const userId = `hook-${Date.now()}`;
155
- await tracked.recordSignupConsents({
156
- userId,
157
- consents: [{ key: 'terms_of_service', granted: true }],
158
- });
159
- expect(calls).toHaveLength(1);
160
- expect(calls[0]).toMatchObject({ userId, key: 'terms_of_service', granted: true });
161
- });
162
- });
@@ -1,16 +0,0 @@
1
- import { Pool } from 'pg';
2
-
3
- export async function setup(): Promise<void> {
4
- const databaseUrl = process.env.DATABASE_URL;
5
- if (!databaseUrl) return;
6
- const { runMigrations, seedStandardConsents } = await import(
7
- '../../src/server/index.js'
8
- );
9
- const pool = new Pool({ connectionString: databaseUrl });
10
- try {
11
- await runMigrations(pool);
12
- await seedStandardConsents(pool, 'TestProduct');
13
- } finally {
14
- await pool.end();
15
- }
16
- }
@@ -1,20 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { applyProductName } from '../../src/server/templates/applyProductName.js';
3
-
4
- describe('applyProductName', () => {
5
- it('replaces {{PRODUCT_NAME}} placeholder', () => {
6
- expect(applyProductName('I agree to the {{PRODUCT_NAME}} Terms.', 'ConstructInv')).toBe(
7
- 'I agree to the ConstructInv Terms.',
8
- );
9
- });
10
-
11
- it('replaces multiple occurrences', () => {
12
- expect(
13
- applyProductName('{{PRODUCT_NAME}} is cool. Use {{PRODUCT_NAME}}.', 'DailyLog'),
14
- ).toBe('DailyLog is cool. Use DailyLog.');
15
- });
16
-
17
- it('leaves strings without placeholder unchanged', () => {
18
- expect(applyProductName('No placeholder here.', 'X')).toBe('No placeholder here.');
19
- });
20
- });
@@ -1,33 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { getAuditTrail } from '../../src/server/lib/getAuditTrail.js';
3
- import type { Pool } from 'pg';
4
-
5
- function makePool(rows: object[]): Pool {
6
- return { query: vi.fn().mockResolvedValue({ rows }) } as unknown as Pool;
7
- }
8
-
9
- describe('getAuditTrail', () => {
10
- it('returns audit entries for a user', async () => {
11
- const entries = [
12
- { id: 1, user_id: 'u1', key: 'terms_of_service', version: 1, granted: true },
13
- { id: 2, user_id: 'u1', key: 'privacy_policy', version: 1, granted: true },
14
- ];
15
- const pool = makePool(entries);
16
- const result = await getAuditTrail(pool, 'u1');
17
- expect(result).toEqual(entries);
18
- });
19
-
20
- it('passes limit=50 by default', async () => {
21
- const pool = makePool([]);
22
- await getAuditTrail(pool, 'u1');
23
- const [, params] = (pool.query as ReturnType<typeof vi.fn>).mock.calls[0];
24
- expect(params[1]).toBe(50);
25
- });
26
-
27
- it('passes custom limit', async () => {
28
- const pool = makePool([]);
29
- await getAuditTrail(pool, 'u1', 10);
30
- const [, params] = (pool.query as ReturnType<typeof vi.fn>).mock.calls[0];
31
- expect(params[1]).toBe(10);
32
- });
33
- });
@@ -1,24 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { hasUserConsented } from '../../src/server/lib/hasUserConsented.js';
3
- import type { Pool } from 'pg';
4
-
5
- function makePool(rows: object[]): Pool {
6
- return { query: vi.fn().mockResolvedValue({ rows }) } as unknown as Pool;
7
- }
8
-
9
- describe('hasUserConsented', () => {
10
- it('returns true when latest record is granted=true', async () => {
11
- const pool = makePool([{ granted: true }]);
12
- expect(await hasUserConsented(pool, 'u1', 'terms_of_service')).toBe(true);
13
- });
14
-
15
- it('returns false when latest record is granted=false', async () => {
16
- const pool = makePool([{ granted: false }]);
17
- expect(await hasUserConsented(pool, 'u1', 'terms_of_service')).toBe(false);
18
- });
19
-
20
- it('returns false when no record exists', async () => {
21
- const pool = makePool([]);
22
- expect(await hasUserConsented(pool, 'u1', 'terms_of_service')).toBe(false);
23
- });
24
- });
@@ -1,24 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { needsConsentUpdate } from '../../src/server/lib/needsConsentUpdate.js';
3
- import type { Pool } from 'pg';
4
-
5
- function makePool(rows: object[]): Pool {
6
- return { query: vi.fn().mockResolvedValue({ rows }) } as unknown as Pool;
7
- }
8
-
9
- describe('needsConsentUpdate', () => {
10
- it('returns definitions that still need consent', async () => {
11
- const missing = [
12
- { id: 1, key: 'terms_of_service', version: 2, required: true, display_text: 'ToS' },
13
- ];
14
- const pool = makePool(missing);
15
- const result = await needsConsentUpdate(pool, 'u1');
16
- expect(result).toEqual(missing);
17
- });
18
-
19
- it('returns empty array when user has all current consents', async () => {
20
- const pool = makePool([]);
21
- const result = await needsConsentUpdate(pool, 'u1');
22
- expect(result).toHaveLength(0);
23
- });
24
- });
@@ -1,41 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { recordConsent } from '../../src/server/lib/recordConsent.js';
3
- import type { Pool } from 'pg';
4
-
5
- function makePool(rows: object[]): Pool {
6
- return { query: vi.fn().mockResolvedValue({ rows }) } as unknown as Pool;
7
- }
8
-
9
- describe('recordConsent', () => {
10
- it('inserts a consent record and returns it', async () => {
11
- const expected = {
12
- id: 1,
13
- user_id: '42',
14
- definition_id: 3,
15
- version: 1,
16
- granted: true,
17
- ip_address: '127.0.0.1',
18
- user_agent: 'vitest',
19
- consented_at: new Date(),
20
- };
21
- const pool = makePool([expected]);
22
- const result = await recordConsent(pool, {
23
- userId: '42',
24
- definitionId: 3,
25
- version: 1,
26
- granted: true,
27
- ipAddress: '127.0.0.1',
28
- userAgent: 'vitest',
29
- });
30
- expect(result).toEqual(expected);
31
- expect(pool.query).toHaveBeenCalledOnce();
32
- });
33
-
34
- it('passes null for missing ipAddress and userAgent', async () => {
35
- const pool = makePool([{ id: 1 }]);
36
- await recordConsent(pool, { userId: 'u1', definitionId: 1, version: 1, granted: false });
37
- const [, params] = (pool.query as ReturnType<typeof vi.fn>).mock.calls[0];
38
- expect(params[4]).toBeNull();
39
- expect(params[5]).toBeNull();
40
- });
41
- });
@@ -1,15 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "ESNext",
5
- "moduleResolution": "Bundler",
6
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
- "jsx": "react-jsx",
8
- "strict": true,
9
- "noEmit": true,
10
- "esModuleInterop": true,
11
- "skipLibCheck": true
12
- },
13
- "include": ["src/client/**/*", "src/shared/**/*"],
14
- "exclude": ["node_modules", "dist"]
15
- }
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "lib": ["ES2022"],
7
- "outDir": "./dist",
8
- "rootDir": "./src",
9
- "declaration": true,
10
- "declarationMap": true,
11
- "sourceMap": true,
12
- "strict": true,
13
- "esModuleInterop": true,
14
- "skipLibCheck": true,
15
- "resolveJsonModule": true
16
- },
17
- "include": ["src/index.ts", "src/server/**/*", "src/shared/**/*"],
18
- "exclude": ["node_modules", "dist", "src/client/**/*"]
19
- }
package/vitest.config.ts DELETED
@@ -1,9 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- environment: 'node',
6
- globalSetup: 'tests/setup/global-setup.ts',
7
- include: ['tests/**/*.test.ts', 'tests/**/*.test.tsx'],
8
- },
9
- });