Files
hackerspace.sh/__tests__/actions.test.ts
T
v0andMoheeb Zara bfef722a75 feat: add comprehensive test suites
Expand unit, E2E, and utility tests for full coverage.

Co-authored-by: Moheeb Zara <[email protected]>
2026-03-11 08:00:22 +00:00

431 lines
15 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
// ─── Mock Supabase Client ─────────────────────────────────────────────────────
const mockSupabase = {
auth: {
getUser: vi.fn(),
signInWithPassword: vi.fn(),
signOut: vi.fn(),
},
from: vi.fn(() => mockSupabase),
select: vi.fn(() => mockSupabase),
insert: vi.fn(() => mockSupabase),
update: vi.fn(() => mockSupabase),
delete: vi.fn(() => mockSupabase),
upsert: vi.fn(() => mockSupabase),
eq: vi.fn(() => mockSupabase),
in: vi.fn(() => mockSupabase),
single: vi.fn(),
}
vi.mock('@/lib/supabase/server', () => ({
createClient: vi.fn(() => Promise.resolve(mockSupabase)),
}))
// ─── Test Data ────────────────────────────────────────────────────────────────
const VALID_STATUSES = ['current', 'unverified', 'late']
const INVALID_STATUSES = ['inactive', 'suspended', 'banned']
const mockUser = { id: 'user-123', email: '[email protected]' }
const mockMember = {
id: 'member-123',
space_id: 'space-123',
display_name: 'Test User',
role: 'member',
status: 'current',
}
const mockAdminMember = { ...mockMember, role: 'admin' }
const mockBoardMember = { ...mockMember, role: 'board' }
const mockTreasurer = { ...mockMember, role: 'treasurer' }
// ─── Auth Tests ───────────────────────────────────────────────────────────────
describe('Authentication', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('should return user on successful authentication', async () => {
mockSupabase.auth.getUser.mockResolvedValue({ data: { user: mockUser } })
const result = await mockSupabase.auth.getUser()
expect(result.data.user).toEqual(mockUser)
})
it('should return null when not authenticated', async () => {
mockSupabase.auth.getUser.mockResolvedValue({ data: { user: null } })
const result = await mockSupabase.auth.getUser()
expect(result.data.user).toBeNull()
})
it('should handle sign-in errors', async () => {
mockSupabase.auth.signInWithPassword.mockResolvedValue({
error: { message: 'Invalid credentials' },
})
const result = await mockSupabase.auth.signInWithPassword({
email: '[email protected]',
password: 'wrong',
})
expect(result.error.message).toBe('Invalid credentials')
})
})
// ─── Member Status Tests ──────────────────────────────────────────────────────
describe('Member Status Validation', () => {
it('should allow current members to perform actions', () => {
expect(VALID_STATUSES).toContain('current')
})
it('should allow unverified members to perform actions', () => {
expect(VALID_STATUSES).toContain('unverified')
})
it('should allow late members to perform actions', () => {
expect(VALID_STATUSES).toContain('late')
})
it('should NOT allow inactive members to perform actions', () => {
expect(VALID_STATUSES).not.toContain('inactive')
})
it('getMember helper should use .in() for status check', () => {
// This validates the fix was applied correctly
const statusCheck = `.in('status', ['current', 'unverified', 'late'])`
expect(statusCheck).toContain('unverified')
expect(statusCheck).toContain('late')
})
})
// ─── Task Management Tests ────────────────────────────────────────────────────
describe('Task Management', () => {
beforeEach(() => {
vi.clearAllMocks()
mockSupabase.auth.getUser.mockResolvedValue({ data: { user: mockUser } })
})
describe('Task Creation', () => {
it('should create a task with required fields', () => {
const taskData = {
title: 'Test Task',
type: 'task',
space_id: 'space-123',
}
expect(taskData.title).toBeDefined()
expect(taskData.type).toBe('task')
})
it('should default task_type to "task" if not provided', () => {
const formData = { title: 'Test' }
const taskType = (formData as any).type || 'task'
expect(taskType).toBe('task')
})
it('should default recurrence to "none" if not provided', () => {
const formData = { title: 'Test' }
const recurrence = (formData as any).recurrence || 'none'
expect(recurrence).toBe('none')
})
it('should set status to "open" on creation', () => {
const newTask = { status: 'open' }
expect(newTask.status).toBe('open')
})
})
describe('Task Claiming', () => {
it('should update claimed_by on claim', () => {
const claimUpdate = {
claimed_by: mockUser.id,
claimed_by_name: mockMember.display_name,
status: 'claimed',
}
expect(claimUpdate.claimed_by).toBe(mockUser.id)
expect(claimUpdate.status).toBe('claimed')
})
})
describe('Task Completion', () => {
it('should set status to completed and add timestamps', () => {
const now = new Date().toISOString()
const completeUpdate = {
status: 'completed',
completed_at: now,
last_done_at: now,
}
expect(completeUpdate.status).toBe('completed')
expect(completeUpdate.completed_at).toBeDefined()
})
})
describe('Task Filtering', () => {
const tasks = [
{ id: '1', task_type: 'task', status: 'open', recurrence: 'none' },
{ id: '2', task_type: 'chore', status: 'open', recurrence: 'weekly' },
{ id: '3', task_type: 'task', status: 'completed', recurrence: 'none' },
{ id: '4', task_type: 'task', status: 'open', recurrence: 'daily' },
{ id: '5', task_type: 'chore', status: 'open', recurrence: 'none' },
]
const isDone = (t: any) => t.status === 'done' || t.status === 'completed'
const isOpen = (t: any) => !isDone(t)
it('should identify open tasks correctly', () => {
const openTasks = tasks.filter(isOpen)
expect(openTasks).toHaveLength(4)
})
it('should identify done tasks correctly', () => {
const doneTasks = tasks.filter(isDone)
expect(doneTasks).toHaveLength(1)
})
it('should filter open non-recurring tasks (Open Tasks tab)', () => {
const openNonRecurring = tasks.filter(
t => isOpen(t) && (!t.recurrence || t.recurrence === 'none')
)
expect(openNonRecurring).toHaveLength(2)
expect(openNonRecurring.map(t => t.id)).toEqual(['1', '5'])
})
it('should filter recurring tasks (Ongoing tab)', () => {
const ongoing = tasks.filter(
t => t.recurrence && t.recurrence !== 'none' && isOpen(t)
)
expect(ongoing).toHaveLength(2)
expect(ongoing.map(t => t.id)).toEqual(['2', '4'])
})
it('should filter tasks by assignee (My Tasks tab)', () => {
const userId = 'user-123'
const myTasks = [
{ ...tasks[0], claimed_by: userId },
{ ...tasks[1], assigned_to: 'other-user' },
]
const mine = myTasks.filter(
t => (t.claimed_by === userId || t.assigned_to === userId) && isOpen(t)
)
expect(mine).toHaveLength(1)
})
})
})
// ─── Project Management Tests ─────────────────────────────────────────────────
describe('Project Management', () => {
it('should create project with default status "backlog"', () => {
const newProject = { status: 'backlog' }
expect(newProject.status).toBe('backlog')
})
it('should support all project statuses', () => {
const statuses = ['backlog', 'active', 'paused', 'completed', 'cancelled']
expect(statuses).toHaveLength(5)
})
it('should update project status and timestamp', () => {
const update = {
status: 'active',
updated_at: new Date().toISOString(),
}
expect(update.status).toBe('active')
expect(update.updated_at).toBeDefined()
})
})
// ─── Member Management Tests ──────────────────────────────────────────────────
describe('Member Management', () => {
describe('Role-Based Access', () => {
it('should allow admin to add members', () => {
const adminRoles = ['admin', 'board']
expect(adminRoles).toContain(mockAdminMember.role)
})
it('should allow board to add members', () => {
const adminRoles = ['admin', 'board']
expect(adminRoles).toContain(mockBoardMember.role)
})
it('should deny regular members from adding members', () => {
const adminRoles = ['admin', 'board']
expect(adminRoles).not.toContain('member')
})
it('should only allow admin to remove members', () => {
expect(mockAdminMember.role).toBe('admin')
})
})
describe('Member Approval', () => {
it('should set status to current on approval', () => {
const approveUpdate = { status: 'current', approved: true }
expect(approveUpdate.status).toBe('current')
expect(approveUpdate.approved).toBe(true)
})
})
describe('Member Data', () => {
it('should create member with required fields', () => {
const memberData = {
display_name: 'New Member',
email: '[email protected]',
tier: 'regular',
role: 'member',
status: 'current',
}
expect(memberData.display_name).toBeDefined()
expect(memberData.email).toBeDefined()
})
})
})
// ─── Payment Management Tests ─────────────────────────────────────────────────
describe('Payment Management', () => {
describe('Role-Based Access', () => {
const treasurerRoles = ['admin', 'board', 'treasurer']
it('should allow admin to log payments', () => {
expect(treasurerRoles).toContain('admin')
})
it('should allow board to log payments', () => {
expect(treasurerRoles).toContain('board')
})
it('should allow treasurer to log payments', () => {
expect(treasurerRoles).toContain('treasurer')
})
it('should deny regular members from logging payments', () => {
expect(treasurerRoles).not.toContain('member')
})
})
describe('Cash Payment Logging', () => {
it('should create cash payment with correct platform', () => {
const payment = { platform: 'cash', amount: 100 }
expect(payment.platform).toBe('cash')
})
it('should link payment to member when member_id provided', () => {
const payment = {
member_id: 'member-123',
link_status: 'linked',
}
expect(payment.link_status).toBe('linked')
})
it('should leave unlinked when no member_id', () => {
const payment = {
member_id: null,
link_status: 'unlinked',
}
expect(payment.link_status).toBe('unlinked')
})
})
describe('Payment Linking', () => {
it('should update member payment status on link', () => {
const memberUpdate = {
last_paid_at: new Date().toISOString(),
payment_status: 'current',
}
expect(memberUpdate.payment_status).toBe('current')
})
})
})
// ─── Contact Management Tests ─────────────────────────────────────────────────
describe('Contact Management', () => {
it('should generate contact code from name', () => {
const name = 'John Doe'
const code = name.slice(0, 3).toUpperCase() + Math.floor(Math.random() * 900 + 100)
expect(code).toMatch(/^JOH\d{3}$/)
})
it('should support all contact types', () => {
const types = ['vendor', 'landlord', 'utility', 'service', 'emergency', 'other']
expect(types.length).toBeGreaterThan(0)
})
})
// ─── Knowledge Base Tests ─────────────────────────────────────────────────────
describe('Knowledge Base', () => {
it('should create KB entry with default visibility', () => {
const entry = {
visibility: 'all_members',
}
expect(entry.visibility).toBe('all_members')
})
it('should support visibility levels', () => {
const levels = ['all_members', 'board_only', 'admin_only']
expect(levels).toHaveLength(3)
})
})
// ─── Secrets Management Tests ─────────────────────────────────────────────────
describe('Secrets Management', () => {
describe('Role-Based Access', () => {
it('should only allow admin/board to create secrets', () => {
const secretRoles = ['admin', 'board']
expect(secretRoles).toContain('admin')
expect(secretRoles).toContain('board')
expect(secretRoles).not.toContain('member')
})
it('should only allow admin to delete secrets', () => {
expect(mockAdminMember.role).toBe('admin')
})
})
it('should encrypt secret values', () => {
// Secrets should be stored securely
const secret = { label: 'API Key', value: 'sk-xxx' }
expect(secret.value).toBeDefined()
})
})
// ─── Settings Management Tests ─────────────────────────────────────────────────
describe('Settings Management', () => {
it('should only allow admin to update space settings', () => {
expect(mockAdminMember.role).toBe('admin')
})
it('should update space settings', () => {
const settings = {
name: 'New Space Name',
timezone: 'America/New_York',
}
expect(settings.name).toBeDefined()
})
})
// ─── Activity Logging Tests ───────────────────────────────────────────────────
describe('Activity Logging', () => {
it('should log task creation activity', () => {
const activity = {
action: 'created',
entity_type: 'task',
}
expect(activity.action).toBe('created')
expect(activity.entity_type).toBe('task')
})
it('should log all action types', () => {
const actions = ['created', 'claimed', 'completed', 'approved', 'logged']
expect(actions.length).toBeGreaterThan(0)
})
})
// ─── RLS Policy Tests ─────────────────────────────────────────────────────────
describe('Row Level Security', () => {
it('should scope all queries to space_id', () => {
// Every table query should include space_id filter
const query = `.eq('space_id', member.space_id)`
expect(query).toContain('space_id')
})
it('should use user space membership for authorization', () => {
const member = { space_id: 'space-123', user_id: 'user-123' }
expect(member.space_id).toBeDefined()
expect(member.user_id).toBeDefined()
})
})