Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
108 lines
3.5 KiB
TypeScript
108 lines
3.5 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
EVENT_SENSITIVE_FIELDS,
|
|
isEventEnded,
|
|
isEventSlugLocked,
|
|
isFieldLocked,
|
|
resolveEventEditPolicy,
|
|
} from '@/features/events/eventEditPolicy'
|
|
|
|
describe('resolveEventEditPolicy', () => {
|
|
it('allows full edits for draft events', () => {
|
|
const policy = resolveEventEditPolicy('draft', 0)
|
|
|
|
expect(policy).toMatchObject({
|
|
canEdit: true,
|
|
hasActiveBookings: false,
|
|
minCapacity: 1,
|
|
lockBehavioralSettings: false,
|
|
})
|
|
expect(policy.lockedFields.size).toBe(0)
|
|
})
|
|
|
|
it('allows full edits for published events without bookings', () => {
|
|
const policy = resolveEventEditPolicy('published', 0)
|
|
|
|
expect(policy.canEdit).toBe(true)
|
|
expect(policy.hasActiveBookings).toBe(false)
|
|
expect(policy.minCapacity).toBe(1)
|
|
expect(policy.lockedFields.size).toBe(0)
|
|
expect(policy.lockBehavioralSettings).toBe(false)
|
|
})
|
|
|
|
it('does not lock the public slug on live statuses (revision path)', () => {
|
|
expect(isEventSlugLocked('draft')).toBe(false)
|
|
expect(isEventSlugLocked('pending_review')).toBe(false)
|
|
expect(isEventSlugLocked('published')).toBe(false)
|
|
expect(isEventSlugLocked('full')).toBe(false)
|
|
expect(isEventSlugLocked('completed')).toBe(true)
|
|
})
|
|
|
|
it('floors capacity at booked plus reserved seats', () => {
|
|
const policy = resolveEventEditPolicy('published', 2, 3)
|
|
|
|
expect(policy.minCapacity).toBe(5)
|
|
})
|
|
|
|
it('locks sensitive fields when a live event has active bookings', () => {
|
|
const policy = resolveEventEditPolicy('published', 3)
|
|
|
|
expect(policy).toMatchObject({
|
|
canEdit: true,
|
|
hasActiveBookings: true,
|
|
minCapacity: 3,
|
|
lockBehavioralSettings: true,
|
|
})
|
|
expect([...policy.lockedFields]).toEqual([...EVENT_SENSITIVE_FIELDS])
|
|
expect(isFieldLocked(policy, 'startsAt')).toBe(true)
|
|
expect(isFieldLocked(policy, 'price')).toBe(true)
|
|
expect(isFieldLocked(policy, 'cancellationFeePercent')).toBe(true)
|
|
expect(isFieldLocked(policy, 'genderRestriction')).toBe(true)
|
|
expect(isFieldLocked(policy, 'ageRestriction')).toBe(true)
|
|
})
|
|
|
|
it('locks the same fields for full events with bookings', () => {
|
|
const policy = resolveEventEditPolicy('full', 5)
|
|
|
|
expect(policy.minCapacity).toBe(5)
|
|
expect(policy.lockedFields.has('address')).toBe(true)
|
|
})
|
|
|
|
it('blocks edits for terminal statuses', () => {
|
|
for (const status of ['cancelled', 'completed']) {
|
|
const policy = resolveEventEditPolicy(status, 2)
|
|
|
|
expect(policy.canEdit).toBe(false)
|
|
expect(policy.minCapacity).toBe(2)
|
|
expect(policy.lockedFields.size).toBe(EVENT_SENSITIVE_FIELDS.length)
|
|
}
|
|
})
|
|
|
|
it('treats pending_review like draft for lock purposes', () => {
|
|
const policy = resolveEventEditPolicy('pending_review', 0)
|
|
|
|
expect(policy.canEdit).toBe(true)
|
|
expect(policy.hasActiveBookings).toBe(false)
|
|
expect(policy.lockedFields.size).toBe(0)
|
|
})
|
|
})
|
|
|
|
describe('isEventEnded', () => {
|
|
const future = new Date(Date.now() + 60 * 60 * 1000).toISOString()
|
|
const past = new Date(Date.now() - 60 * 60 * 1000).toISOString()
|
|
|
|
it('is true for terminal statuses even when endsAt is still in the future', () => {
|
|
expect(isEventEnded('completed', future)).toBe(true)
|
|
expect(isEventEnded('cancelled', future)).toBe(true)
|
|
})
|
|
|
|
it('is true when endsAt has passed on a live event', () => {
|
|
expect(isEventEnded('published', past)).toBe(true)
|
|
})
|
|
|
|
it('is false for a live event that has not ended yet', () => {
|
|
expect(isEventEnded('published', future)).toBe(false)
|
|
})
|
|
})
|