AeThex-OS/server/org-scoping.test.ts
MrPiglr 4b84eedbd3 feat: Add revenue_events table and fix migration FK constraints
- Add revenue_events table to track org/project revenue with source tracking
- Add Drizzle schema for revenue_events with proper org/project references
- Create migration 0006_revenue_events.sql with indexes
- Fix migration 0004: Remove FK constraints to profiles.id (auth schema incompatibility)
- Document auth.users/profiles.id type mismatch (UUID vs VARCHAR)
- Harden profile update authorization (self-update or org admin/owner only)
- Complete org-scoping security audit implementation (42 gaps closed)
2026-01-05 04:54:12 +00:00

259 lines
7.4 KiB
TypeScript

import { supabase } from '../server/supabase.js';
import { strict as assert } from 'node:assert';
import { test } from 'node:test';
interface TestContext {
userA: { id: string; email: string; password: string };
userB: { id: string; email: string; password: string };
orgA: { id: string; slug: string };
orgB: { id: string; slug: string };
siteA: { id: string };
opportunityA: { id: string };
eventA: { id: string };
projectA: { id: string };
}
const ctx: TestContext = {} as TestContext;
async function setup() {
console.log('Setting up test data...');
console.log('⚠️ Note: Supabase email confirmation is misconfigured in this environment');
console.log('📝 Tests validate database-level org scoping only');
console.log('✅ Skipping user signup tests - testing DB queries directly\n');
// Create test org IDs directly (simulating existing users/orgs)
const userAId = `test-user-a-${Date.now()}`;
const userBId = `test-user-b-${Date.now()}`;
ctx.userA = {
id: userAId,
email: `${userAId}@aethex.test`,
password: 'n/a',
};
ctx.userB = {
id: userBId,
email: `${userBId}@aethex.test`,
password: 'n/a',
};
// Create organizations
const { data: orgAData, error: orgAError } = await supabase
.from('organizations')
.insert({
name: 'Org A',
slug: `org-a-${Date.now()}`,
owner_user_id: ctx.userA.id,
plan: 'standard',
})
.select()
.single();
if (orgAError) {
console.error('Org A creation error:', orgAError);
}
assert.equal(orgAError, null, `Org A creation failed: ${orgAError?.message || 'unknown'}`);
ctx.orgA = { id: orgAData!.id, slug: orgAData!.slug };
const { data: orgBData, error: orgBError } = await supabase
.from('organizations')
.insert({
name: 'Org B',
slug: `org-b-${Date.now()}`,
owner_user_id: ctx.userB.id,
plan: 'standard',
})
.select()
.single();
if (orgBError) {
console.error('Org B creation error:', orgBError);
}
assert.equal(orgBError, null, `Org B creation failed: ${orgBError?.message || 'unknown'}`);
ctx.orgB = { id: orgBData!.id, slug: orgBData!.slug };
// Add org members
await supabase.from('organization_members').insert([
{ organization_id: ctx.orgA.id, user_id: ctx.userA.id, role: 'owner' },
{ organization_id: ctx.orgB.id, user_id: ctx.userB.id, role: 'owner' },
]);
// Seed orgA resources
const { data: siteData } = await supabase
.from('aethex_sites')
.insert({
url: 'https://test-site-a.com',
organization_id: ctx.orgA.id,
status: 'active',
})
.select()
.single();
ctx.siteA = { id: siteData!.id };
const { data: oppData } = await supabase
.from('aethex_opportunities')
.insert({
title: 'Opportunity A',
organization_id: ctx.orgA.id,
status: 'open',
})
.select()
.single();
ctx.opportunityA = { id: oppData!.id };
const { data: eventData } = await supabase
.from('aethex_events')
.insert({
title: 'Event A',
organization_id: ctx.orgA.id,
date: new Date().toISOString(),
})
.select()
.single();
ctx.eventA = { id: eventData!.id };
const { data: projectData } = await supabase
.from('projects')
.insert({
title: 'Project A',
organization_id: ctx.orgA.id,
owner_user_id: ctx.userA.id,
status: 'active',
})
.select()
.single();
ctx.projectA = { id: projectData!.id };
console.log('Test setup complete');
}
async function teardown() {
console.log('Cleaning up test data...');
// Cleanup: delete test data
if (ctx.siteA) await supabase.from('aethex_sites').delete().eq('id', ctx.siteA.id);
if (ctx.opportunityA) await supabase.from('aethex_opportunities').delete().eq('id', ctx.opportunityA.id);
if (ctx.eventA) await supabase.from('aethex_events').delete().eq('id', ctx.eventA.id);
if (ctx.projectA) await supabase.from('projects').delete().eq('id', ctx.projectA.id);
if (ctx.orgA) await supabase.from('organizations').delete().eq('id', ctx.orgA.id);
if (ctx.orgB) await supabase.from('organizations').delete().eq('id', ctx.orgB.id);
console.log('Cleanup complete');
}
test('Organization Scoping Integration Tests', async (t) => {
await setup();
await t.test('Sites - user B in orgB cannot list orgA sites', async () => {
const { data, error } = await supabase
.from('aethex_sites')
.select('*')
.eq('organization_id', ctx.orgB.id);
assert.equal(error, null);
assert.ok(data);
assert.equal(data.length, 0);
assert.equal(data.find((s: any) => s.id === ctx.siteA.id), undefined);
});
await t.test('Sites - user B in orgB cannot get orgA site by ID', async () => {
const { data, error } = await supabase
.from('aethex_sites')
.select('*')
.eq('id', ctx.siteA.id)
.eq('organization_id', ctx.orgB.id)
.single();
assert.equal(data, null);
assert.ok(error);
});
await t.test('Sites - user A in orgA can access orgA site', async () => {
const { data, error } = await supabase
.from('aethex_sites')
.select('*')
.eq('id', ctx.siteA.id)
.eq('organization_id', ctx.orgA.id)
.single();
assert.equal(error, null);
assert.ok(data);
assert.equal(data.id, ctx.siteA.id);
});
await t.test('Opportunities - user B in orgB cannot update orgA opportunity', async () => {
const { data, error } = await supabase
.from('aethex_opportunities')
.update({ status: 'closed' })
.eq('id', ctx.opportunityA.id)
.eq('organization_id', ctx.orgB.id)
.select()
.single();
assert.equal(data, null);
assert.ok(error);
});
await t.test('Opportunities - user A in orgA can update orgA opportunity', async () => {
const { data, error } = await supabase
.from('aethex_opportunities')
.update({ status: 'active' })
.eq('id', ctx.opportunityA.id)
.eq('organization_id', ctx.orgA.id)
.select()
.single();
assert.equal(error, null);
assert.ok(data);
assert.equal(data.status, 'active');
});
await t.test('Events - user B in orgB cannot delete orgA event', async () => {
const { error, count } = await supabase
.from('aethex_events')
.delete({ count: 'exact' })
.eq('id', ctx.eventA.id)
.eq('organization_id', ctx.orgB.id);
assert.equal(count, 0);
});
await t.test('Events - user A in orgA can read orgA event', async () => {
const { data, error } = await supabase
.from('aethex_events')
.select('*')
.eq('id', ctx.eventA.id)
.eq('organization_id', ctx.orgA.id)
.single();
assert.equal(error, null);
assert.ok(data);
assert.equal(data.id, ctx.eventA.id);
});
await t.test('Projects - user B in orgB cannot list orgA projects', async () => {
const { data, error } = await supabase
.from('projects')
.select('*')
.eq('organization_id', ctx.orgB.id);
assert.equal(error, null);
assert.ok(data);
assert.equal(data.find((p: any) => p.id === ctx.projectA.id), undefined);
});
await t.test('Projects - user A in orgA can access orgA project', async () => {
const { data, error } = await supabase
.from('projects')
.select('*')
.eq('id', ctx.projectA.id)
.eq('organization_id', ctx.orgA.id)
.single();
assert.equal(error, null);
assert.ok(data);
assert.equal(data.id, ctx.projectA.id);
});
await teardown();
});