Skip to content

Commit 0edea55

Browse files
Copilothotlong
andcommitted
test(plugin-multitenancy): add comprehensive E2E demo test
- Demonstrates complete tenant isolation workflow - Shows auto-set tenant_id, query filtering, cross-tenant protection - Validates exempt objects and audit logging - All 59 tests passing Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent c02f118 commit 0edea55

1 file changed

Lines changed: 143 additions & 0 deletions

File tree

  • packages/foundation/plugin-multitenancy/__tests__
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* Multi-Tenancy Plugin - End-to-End Demo
3+
*
4+
* This demo shows the plugin in action with a real-world scenario
5+
*/
6+
7+
import { describe, it, expect, beforeEach } from 'vitest';
8+
import { MultiTenancyPlugin, TenantIsolationError } from '../src';
9+
10+
describe('Multi-Tenancy E2E Demo', () => {
11+
it('should demonstrate complete tenant isolation workflow', async () => {
12+
const plugin = new MultiTenancyPlugin({
13+
tenantField: 'tenant_id',
14+
strictMode: true,
15+
exemptObjects: ['users'],
16+
enableAudit: true,
17+
});
18+
19+
// Simulate kernel installation
20+
const mockHooks = new Map<string, Function[]>();
21+
const mockKernel = {
22+
hooks: {
23+
register: (name: string, handler: Function) => {
24+
if (!mockHooks.has(name)) {
25+
mockHooks.set(name, []);
26+
}
27+
mockHooks.get(name)!.push(handler);
28+
},
29+
},
30+
};
31+
32+
await plugin.install({
33+
engine: mockKernel,
34+
hook: (name: string, handler: Function) => {
35+
if (!mockHooks.has(name)) {
36+
mockHooks.set(name, []);
37+
}
38+
mockHooks.get(name)!.push(handler);
39+
},
40+
});
41+
42+
// Helper to trigger hooks
43+
const triggerHook = async (name: string, context: any) => {
44+
const handlers = mockHooks.get(name) || [];
45+
for (const handler of handlers) {
46+
await handler(context);
47+
}
48+
};
49+
50+
// SCENARIO 1: Tenant A creates an account
51+
console.log('\n=== Scenario 1: Tenant A creates account ===');
52+
const createContext = {
53+
objectName: 'accounts',
54+
data: {
55+
name: 'Acme Corporation',
56+
industry: 'Technology',
57+
},
58+
tenantId: 'tenant-a',
59+
user: { id: 'user-1' },
60+
};
61+
62+
await triggerHook('beforeCreate', createContext);
63+
64+
// Verify tenant_id was auto-set
65+
expect(createContext.data.tenant_id).toBe('tenant-a');
66+
console.log('✓ Auto-set tenant_id:', createContext.data.tenant_id);
67+
68+
// SCENARIO 2: Tenant A queries their accounts
69+
console.log('\n=== Scenario 2: Tenant A queries accounts ===');
70+
const findContext = {
71+
objectName: 'accounts',
72+
query: { industry: 'Technology' },
73+
tenantId: 'tenant-a',
74+
};
75+
76+
await triggerHook('beforeFind', findContext);
77+
78+
// Verify filter was injected
79+
expect(findContext.query.tenant_id).toBe('tenant-a');
80+
console.log('✓ Injected tenant filter:', findContext.query);
81+
82+
// SCENARIO 3: Tenant B tries to access Tenant A's account
83+
console.log('\n=== Scenario 3: Cross-tenant update attempt ===');
84+
const crossTenantUpdate = {
85+
objectName: 'accounts',
86+
data: { name: 'Hacked Name' },
87+
previousData: {
88+
id: 1,
89+
name: 'Acme Corporation',
90+
tenant_id: 'tenant-a', // This belongs to Tenant A
91+
},
92+
tenantId: 'tenant-b', // But Tenant B is trying to update it
93+
};
94+
95+
// Should throw error
96+
await expect(
97+
triggerHook('beforeUpdate', crossTenantUpdate)
98+
).rejects.toThrow(TenantIsolationError);
99+
100+
console.log('✓ Cross-tenant update blocked');
101+
102+
// SCENARIO 4: Exempt object access
103+
console.log('\n=== Scenario 4: Exempt object (users) ===');
104+
const userQuery = {
105+
objectName: 'users',
106+
query: { email: 'test@example.com' },
107+
tenantId: 'tenant-a',
108+
};
109+
110+
await triggerHook('beforeFind', userQuery);
111+
112+
// Should NOT inject tenant_id for exempt objects
113+
expect(userQuery.query.tenant_id).toBeUndefined();
114+
console.log('✓ Exempt object skipped tenant filter');
115+
116+
// SCENARIO 5: Audit log verification
117+
console.log('\n=== Scenario 5: Audit logs ===');
118+
const logs = plugin.getAuditLogs();
119+
120+
expect(logs.length).toBeGreaterThan(0);
121+
122+
// Find the create operation
123+
const createLog = logs.find(
124+
log => log.operation === 'create' && log.objectName === 'accounts'
125+
);
126+
expect(createLog).toBeDefined();
127+
expect(createLog?.tenantId).toBe('tenant-a');
128+
expect(createLog?.allowed).toBe(true);
129+
130+
// Find the denied update
131+
const deniedLog = logs.find(
132+
log => log.operation === 'update' && log.allowed === false
133+
);
134+
expect(deniedLog).toBeDefined();
135+
expect(deniedLog?.reason).toContain('CROSS_TENANT');
136+
137+
console.log('✓ Audit logs captured:', logs.length, 'entries');
138+
console.log(' - Create allowed:', createLog?.allowed);
139+
console.log(' - Update denied:', deniedLog?.allowed === false);
140+
141+
console.log('\n=== Demo Complete ===\n');
142+
});
143+
});

0 commit comments

Comments
 (0)