Quick fixes + logo better

This commit is contained in:
AlacrisDevs
2026-02-09 18:05:09 +02:00
parent 046d4bd098
commit c2d3caaa5a
17 changed files with 1400 additions and 288 deletions

View File

@@ -6,11 +6,19 @@ import * as path from 'path';
* Global teardown: delete all test-created data from Supabase.
* Runs after all Playwright tests complete.
*
* Matches documents/folders/kanbans by name prefixes used in tests:
* "Test Folder", "Test Doc", "Test Board", "Nav Folder", "Rename Me", "Renamed"
* Matches kanban boards by name prefix: "PW Board", "Board A", "Board B"
* Matches org_invites by email pattern: "playwright-test-*@example.com"
* Matches org_roles by name prefix: "Tester"
* Uses SUPABASE_SERVICE_ROLE_KEY to bypass RLS for reliable cleanup.
* Falls back to anon key + password auth if service role key is unavailable.
*
* Cleanup targets (by name prefix):
* Documents: "Test Folder", "Test Doc", "Test Board", "Nav Folder", "Rename Me", "Renamed"
* Boards: "PW Board", "PW Card Board", "PW Detail Board", "Board A", "Board B", "Perf Test"
* Events: "PW Event", "PW Detail", "PW Delete", "PW Test Event", "PW Finance"
* Invites: "playwright-test-*@example.com"
* Roles: "Tester"
* Tags: "PW Tag"
* Sponsors: "PW Sponsor"
* Contacts: "PW Contact", "PW Vendor"
* Org events: "PW Test Event", "PW Finance" (cascades to depts, modules, budget, etc.)
*/
// Load .env manually since we're outside Vite
@@ -32,34 +40,67 @@ function loadEnv() {
loadEnv();
const SUPABASE_URL = process.env.PUBLIC_SUPABASE_URL || '';
const SUPABASE_KEY = process.env.PUBLIC_SUPABASE_ANON_KEY || '';
const SUPABASE_ANON_KEY = process.env.PUBLIC_SUPABASE_ANON_KEY || '';
const SUPABASE_SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY || '';
// Name prefixes used by tests when creating data
const DOC_PREFIXES = ['Test Folder', 'Test Doc', 'Test Board', 'Nav Folder', 'Rename Me', 'Renamed'];
const BOARD_PREFIXES = ['PW Board', 'PW Card Board', 'PW Detail Board', 'Board A', 'Board B'];
const BOARD_PREFIXES = ['PW Board', 'PW Card Board', 'PW Detail Board', 'Board A', 'Board B', 'Perf Test'];
const ROLE_PREFIX = 'Tester';
const TAG_PREFIX = 'PW Tag';
const EVENT_PREFIXES = ['PW Event', 'PW Detail', 'PW Delete', 'PW Test Event', 'PW Finance'];
const CALENDAR_EVENT_PREFIXES = ['PW Event', 'PW Detail', 'PW Delete'];
const ORG_EVENT_PREFIXES = ['PW Test Event', 'PW Finance'];
const SPONSOR_PREFIXES = ['PW Sponsor'];
const CONTACT_PREFIXES = ['PW Contact', 'PW Vendor'];
const INVITE_EMAIL_PATTERN = 'playwright-test-%@example.com';
/** Helper: delete rows by prefix from a table column */
async function deleteByPrefix(
supabase: ReturnType<typeof createClient>,
table: string,
column: string,
prefix: string,
filterCol?: string,
filterVal?: string,
): Promise<number> {
let query = (supabase as any).from(table).select('id').ilike(column, `${prefix}%`);
if (filterCol && filterVal) query = query.eq(filterCol, filterVal);
const { data } = await query;
if (!data || data.length === 0) return 0;
const ids = data.map((r: any) => r.id);
const { error } = await (supabase as any).from(table).delete().in('id', ids);
if (error) {
console.log(`[cleanup] Failed to delete from ${table} (prefix "${prefix}"): ${error.message}`);
return 0;
}
return data.length;
}
export default async function globalTeardown() {
if (!SUPABASE_KEY) {
console.log('[cleanup] No SUPABASE_ANON_KEY - skipping cleanup');
if (!SUPABASE_ANON_KEY && !SUPABASE_SERVICE_ROLE_KEY) {
console.log('[cleanup] No Supabase keys found - skipping cleanup');
return;
}
// Authenticate using the test user credentials directly
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
const { error: authError } = await supabase.auth.signInWithPassword({
email: 'tipilan@ituk.ee',
password: 'gu&u6QTMbJK7nT',
});
let supabase: ReturnType<typeof createClient>;
if (authError) {
console.log('[cleanup] Auth failed - skipping cleanup:', authError.message);
return;
// Prefer service role key (bypasses RLS) for reliable cleanup
if (SUPABASE_SERVICE_ROLE_KEY) {
console.log('[cleanup] Using service role key (RLS bypassed)');
supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, {
auth: { autoRefreshToken: false, persistSession: false },
});
} else {
console.log('[cleanup] No service role key, falling back to anon key + auth');
supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
const { error: authError } = await supabase.auth.signInWithPassword({
email: 'tipilan@ituk.ee',
password: 'gu&u6QTMbJK7nT',
});
if (authError) {
console.log('[cleanup] Auth failed - skipping cleanup:', authError.message);
return;
}
}
// Get the org ID for root-test
@@ -74,160 +115,71 @@ export default async function globalTeardown() {
return;
}
const orgId = org.id;
const orgId = (org as any).id;
let totalDeleted = 0;
// 1. Delete test documents (folders, docs, kanbans)
// 1. Delete test documents (folders, docs, kanbans) — cascades to content
for (const prefix of DOC_PREFIXES) {
const { data: docs } = await supabase
.from('documents')
.select('id')
.eq('org_id', orgId)
.ilike('name', `${prefix}%`);
if (docs && docs.length > 0) {
const ids = docs.map(d => d.id);
const { error } = await supabase
.from('documents')
.delete()
.in('id', ids);
if (!error) {
totalDeleted += docs.length;
} else {
console.log(`[cleanup] Failed to delete docs with prefix "${prefix}":`, error.message);
}
}
totalDeleted += await deleteByPrefix(supabase, 'documents', 'name', prefix, 'org_id', orgId);
}
// 2. Delete test kanban boards
// 2. Delete test kanban boards — cascades to columns → cards → assignees
for (const prefix of BOARD_PREFIXES) {
const { data: boards } = await supabase
.from('kanban_boards')
.select('id')
.eq('org_id', orgId)
.ilike('name', `${prefix}%`);
if (boards && boards.length > 0) {
const ids = boards.map(b => b.id);
const { error } = await supabase
.from('kanban_boards')
.delete()
.in('id', ids);
if (!error) {
totalDeleted += boards.length;
} else {
console.log(`[cleanup] Failed to delete boards with prefix "${prefix}":`, error.message);
}
}
totalDeleted += await deleteByPrefix(supabase, 'kanban_boards', 'name', prefix, 'org_id', orgId);
}
// 3. Delete test invites (playwright-test-*@example.com)
const { data: invites } = await supabase
// 3. Delete test invites
const { data: invites } = await (supabase as any)
.from('org_invites')
.select('id')
.eq('org_id', orgId)
.ilike('email', INVITE_EMAIL_PATTERN);
if (invites && invites.length > 0) {
const ids = invites.map(i => i.id);
await supabase.from('org_invites').delete().in('id', ids);
totalDeleted += invites.length;
const { error } = await (supabase as any).from('org_invites').delete().in('id', invites.map((i: any) => i.id));
if (!error) totalDeleted += invites.length;
else console.log('[cleanup] Failed to delete invites:', error.message);
}
// 4. Delete test roles
const { data: roles } = await supabase
.from('org_roles')
.select('id')
.eq('org_id', orgId)
.ilike('name', `${ROLE_PREFIX}%`);
if (roles && roles.length > 0) {
const ids = roles.map(r => r.id);
await supabase.from('org_roles').delete().in('id', ids);
totalDeleted += roles.length;
}
totalDeleted += await deleteByPrefix(supabase, 'org_roles', 'name', ROLE_PREFIX, 'org_id', orgId);
// 5. Delete test tags
const { data: tags } = await supabase
.from('tags')
totalDeleted += await deleteByPrefix(supabase, 'tags', 'name', TAG_PREFIX, 'org_id', orgId);
// 6. Delete test calendar events (simple calendar events, not org events)
for (const prefix of CALENDAR_EVENT_PREFIXES) {
totalDeleted += await deleteByPrefix(supabase, 'calendar_events', 'title', prefix, 'org_id', orgId);
}
// 7. Delete test org events — cascades to departments → modules → budget items, checklists, notes, etc.
for (const prefix of ORG_EVENT_PREFIXES) {
totalDeleted += await deleteByPrefix(supabase, 'events', 'name', prefix, 'org_id', orgId);
}
// 8. Delete test sponsors
for (const prefix of SPONSOR_PREFIXES) {
totalDeleted += await deleteByPrefix(supabase, 'sponsors', 'name', prefix);
}
// 9. Delete test org contacts
for (const prefix of CONTACT_PREFIXES) {
totalDeleted += await deleteByPrefix(supabase, 'org_contacts', 'name', prefix, 'org_id', orgId);
}
// 10. Delete test activity log entries (from test actions)
const { data: activityLogs } = await (supabase as any)
.from('activity_log')
.select('id')
.eq('org_id', orgId)
.ilike('name', `${TAG_PREFIX}%`);
.gte('created_at', new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString());
if (tags && tags.length > 0) {
const ids = tags.map(t => t.id);
await supabase.from('tags').delete().in('id', ids);
totalDeleted += tags.length;
}
// 6. Delete test calendar events
for (const prefix of EVENT_PREFIXES) {
const { data: events } = await supabase
.from('calendar_events')
.select('id')
.eq('org_id', orgId)
.ilike('title', `${prefix}%`);
if (events && events.length > 0) {
const ids = events.map(e => e.id);
const { error } = await supabase
.from('calendar_events')
.delete()
.in('id', ids);
if (!error) {
totalDeleted += events.length;
} else {
console.log(`[cleanup] Failed to delete events with prefix "${prefix}":`, error.message);
}
}
}
// 7. Delete test sponsors (via name prefix)
for (const prefix of SPONSOR_PREFIXES) {
const { data: sponsors } = await (supabase as any)
.from('sponsors')
.select('id')
.ilike('name', `${prefix}%`);
if (sponsors && sponsors.length > 0) {
const ids = sponsors.map((s: any) => s.id);
const { error } = await (supabase as any)
.from('sponsors')
.delete()
.in('id', ids);
if (!error) {
totalDeleted += sponsors.length;
} else {
console.log(`[cleanup] Failed to delete sponsors with prefix "${prefix}":`, error.message);
}
}
}
// 8. Delete test org contacts (via name prefix)
for (const prefix of CONTACT_PREFIXES) {
const { data: contacts } = await (supabase as any)
.from('org_contacts')
.select('id')
.eq('org_id', orgId)
.ilike('name', `${prefix}%`);
if (contacts && contacts.length > 0) {
const ids = contacts.map((c: any) => c.id);
const { error } = await (supabase as any)
.from('org_contacts')
.delete()
.in('id', ids);
if (!error) {
totalDeleted += contacts.length;
} else {
console.log(`[cleanup] Failed to delete contacts with prefix "${prefix}":`, error.message);
}
}
if (activityLogs && activityLogs.length > 0) {
const { error } = await (supabase as any)
.from('activity_log')
.delete()
.in('id', activityLogs.map((l: any) => l.id));
if (!error) totalDeleted += activityLogs.length;
}
if (totalDeleted > 0) {