cms.c2sgmbh/tests/int/videos.int.spec.ts
Martin Porwoll 913897c87c feat: add comprehensive video feature with collections, hooks, and tests
Video Feature Implementation:
- Add Videos and VideoCategories collections with multi-tenant support
- Extend VideoBlock with library/upload/embed sources and playback options
- Add featuredVideo group to Posts collection with processed embed URLs

Hooks & Validation:
- Add processFeaturedVideo hook for URL parsing and privacy mode embedding
- Add createSlugValidationHook for tenant-scoped slug uniqueness
- Add video-utils library (parseVideoUrl, generateEmbedUrl, formatDuration)

Testing:
- Add 84 unit tests for video-utils (URL parsing, duration, embed generation)
- Add 14 integration tests for Videos collection CRUD and slug validation

Database:
- Migration for videos, video_categories tables with locales
- Migration for Posts featuredVideo processed fields
- Update payload internal tables for new collections

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 10:48:33 +00:00

298 lines
7.8 KiB
TypeScript

import { getPayload, Payload } from 'payload'
import config from '@/payload.config'
import { describe, it, beforeAll, afterAll, expect } from 'vitest'
let payload: Payload
let testTenantId: number
let testVideoId: number
let testCategoryId: number
describe('Videos Collection API', () => {
beforeAll(async () => {
const payloadConfig = await config
payload = await getPayload({ config: payloadConfig })
// Find or use existing tenant for testing
const tenants = await payload.find({
collection: 'tenants',
limit: 1,
})
if (tenants.docs.length > 0) {
testTenantId = tenants.docs[0].id as number
} else {
// Create a test tenant if none exists
const tenant = await payload.create({
collection: 'tenants',
data: {
name: 'Test Tenant for Videos',
slug: 'test-videos-tenant',
domains: [{ domain: 'test-videos.local' }],
},
})
testTenantId = tenant.id as number
}
})
afterAll(async () => {
// Cleanup: Delete test video and category if created
if (testVideoId) {
try {
await payload.delete({
collection: 'videos',
id: testVideoId,
})
} catch {
// Ignore if already deleted
}
}
if (testCategoryId) {
try {
await payload.delete({
collection: 'video-categories',
id: testCategoryId,
})
} catch {
// Ignore if already deleted
}
}
})
describe('VideoCategories CRUD', () => {
it('creates a video category', async () => {
const category = await payload.create({
collection: 'video-categories',
data: {
name: 'Test Category',
slug: 'test-category-' + Date.now(),
tenant: testTenantId,
isActive: true,
},
})
expect(category).toBeDefined()
expect(category.id).toBeDefined()
expect(category.name).toBe('Test Category')
testCategoryId = category.id as number
})
it('finds video categories', async () => {
const categories = await payload.find({
collection: 'video-categories',
where: {
tenant: { equals: testTenantId },
},
})
expect(categories).toBeDefined()
expect(categories.docs).toBeInstanceOf(Array)
expect(categories.docs.length).toBeGreaterThan(0)
})
it('updates a video category', async () => {
const updated = await payload.update({
collection: 'video-categories',
id: testCategoryId,
data: {
name: 'Updated Category Name',
},
})
expect(updated.name).toBe('Updated Category Name')
})
})
describe('Videos CRUD', () => {
it('creates a video with YouTube embed', async () => {
const video = await payload.create({
collection: 'videos',
data: {
title: 'Test Video',
slug: 'test-video-' + Date.now(),
tenant: testTenantId,
source: 'youtube',
embedUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
status: 'draft',
},
})
expect(video).toBeDefined()
expect(video.id).toBeDefined()
expect(video.title).toBe('Test Video')
expect(video.source).toBe('youtube')
// Check that videoId was extracted by hook
expect(video.videoId).toBe('dQw4w9WgXcQ')
testVideoId = video.id as number
})
it('creates a video with Vimeo embed', async () => {
const video = await payload.create({
collection: 'videos',
data: {
title: 'Test Vimeo Video',
slug: 'test-vimeo-video-' + Date.now(),
tenant: testTenantId,
source: 'vimeo',
embedUrl: 'https://vimeo.com/76979871',
status: 'draft',
},
})
expect(video).toBeDefined()
expect(video.videoId).toBe('76979871')
// Cleanup this extra video
await payload.delete({
collection: 'videos',
id: video.id,
})
})
it('finds videos by tenant', async () => {
const videos = await payload.find({
collection: 'videos',
where: {
tenant: { equals: testTenantId },
},
})
expect(videos).toBeDefined()
expect(videos.docs).toBeInstanceOf(Array)
expect(videos.docs.length).toBeGreaterThan(0)
})
it('finds videos by status', async () => {
const videos = await payload.find({
collection: 'videos',
where: {
and: [{ tenant: { equals: testTenantId } }, { status: { equals: 'draft' } }],
},
})
expect(videos).toBeDefined()
expect(videos.docs.every((v) => v.status === 'draft')).toBe(true)
})
it('updates a video', async () => {
const updated = await payload.update({
collection: 'videos',
id: testVideoId,
data: {
title: 'Updated Video Title',
status: 'published',
},
})
expect(updated.title).toBe('Updated Video Title')
expect(updated.status).toBe('published')
})
it('associates video with category', async () => {
const updated = await payload.update({
collection: 'videos',
id: testVideoId,
data: {
category: testCategoryId,
},
})
expect(updated.category).toBeDefined()
})
it('finds video by slug', async () => {
// First get the video to know its slug
const video = await payload.findByID({
collection: 'videos',
id: testVideoId,
})
const found = await payload.find({
collection: 'videos',
where: {
and: [{ tenant: { equals: testTenantId } }, { slug: { equals: video.slug } }],
},
})
expect(found.docs.length).toBe(1)
expect(found.docs[0].id).toBe(testVideoId)
})
})
describe('Slug Validation', () => {
it('prevents duplicate slugs within same tenant', async () => {
// Get the existing video's slug
const existingVideo = await payload.findByID({
collection: 'videos',
id: testVideoId,
})
// Try to create another video with the same slug
await expect(
payload.create({
collection: 'videos',
data: {
title: 'Duplicate Slug Video',
slug: existingVideo.slug,
tenant: testTenantId,
source: 'youtube',
embedUrl: 'https://www.youtube.com/watch?v=abc123',
status: 'draft',
},
})
).rejects.toThrow()
})
it('prevents duplicate category slugs within same tenant', async () => {
// Get the existing category's slug
const existingCategory = await payload.findByID({
collection: 'video-categories',
id: testCategoryId,
})
// Try to create another category with the same slug
await expect(
payload.create({
collection: 'video-categories',
data: {
name: 'Duplicate Category',
slug: existingCategory.slug,
tenant: testTenantId,
},
})
).rejects.toThrow()
})
})
describe('Video Deletion', () => {
it('deletes a video', async () => {
const deleted = await payload.delete({
collection: 'videos',
id: testVideoId,
})
expect(deleted.id).toBe(testVideoId)
// Verify it's gone
const found = await payload.find({
collection: 'videos',
where: {
id: { equals: testVideoId },
},
})
expect(found.docs.length).toBe(0)
testVideoId = 0 // Mark as deleted so afterAll doesn't try again
})
it('deletes a video category', async () => {
const deleted = await payload.delete({
collection: 'video-categories',
id: testCategoryId,
})
expect(deleted.id).toBe(testCategoryId)
testCategoryId = 0 // Mark as deleted
})
})
})