mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 19:44:12 +00:00
Merge branch 'feature/nextjs-16-upgrade' - TypeScript and test fixes
Merged changes: - Reverted Next.js 16 upgrade (Payload CMS 3.68.4 incompatible) - Fixed all TypeScript errors in test files - ESLint and typecheck now pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
commit
91d00b016e
11 changed files with 42 additions and 29 deletions
|
|
@ -16,7 +16,7 @@ const nextConfig = {
|
||||||
workerThreads: false,
|
workerThreads: false,
|
||||||
cpus: 1,
|
cpus: 1,
|
||||||
},
|
},
|
||||||
// Your Next.js config here
|
// Webpack configuration for TypeScript/ESM compatibility
|
||||||
webpack: (webpackConfig) => {
|
webpack: (webpackConfig) => {
|
||||||
webpackConfig.resolve.extensionAlias = {
|
webpackConfig.resolve.extensionAlias = {
|
||||||
'.cjs': ['.cts', '.cjs'],
|
'.cjs': ['.cts', '.cjs'],
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
"vitest": "4.0.15"
|
"vitest": "4.0.15"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.20.2 || >=20.9.0",
|
"node": ">=20.9.0",
|
||||||
"pnpm": "^9 || ^10"
|
"pnpm": "^9 || ^10"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,10 @@ export interface MockTenant {
|
||||||
domains?: Array<{ domain: string }>
|
domains?: Array<{ domain: string }>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MockPayloadRequest extends Partial<PayloadRequest> {
|
// Note: Not extending PayloadRequest to allow flexible mock types for testing
|
||||||
|
export interface MockPayloadRequest {
|
||||||
user?: MockUser | null
|
user?: MockUser | null
|
||||||
|
// Allow both Headers and plain object for testing different header formats
|
||||||
headers: Headers | Record<string, string | string[] | undefined>
|
headers: Headers | Record<string, string | string[] | undefined>
|
||||||
payload: {
|
payload: {
|
||||||
find: ReturnType<typeof vi.fn>
|
find: ReturnType<typeof vi.fn>
|
||||||
|
|
@ -126,10 +128,10 @@ export function createMockPayloadRequest(
|
||||||
tenants?: MockTenant[]
|
tenants?: MockTenant[]
|
||||||
} = {},
|
} = {},
|
||||||
): MockPayloadRequest {
|
): MockPayloadRequest {
|
||||||
const headers: Record<string, string | string[] | undefined> = {}
|
const headers = new Headers()
|
||||||
|
|
||||||
if (options.host) {
|
if (options.host) {
|
||||||
headers['host'] = options.host
|
headers.set('host', options.host)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock payload.find to resolve tenant from host
|
// Mock payload.find to resolve tenant from host
|
||||||
|
|
@ -306,9 +308,11 @@ export async function executeAccess(
|
||||||
data?: Record<string, unknown>
|
data?: Record<string, unknown>
|
||||||
} = {},
|
} = {},
|
||||||
): Promise<AccessResult> {
|
): Promise<AccessResult> {
|
||||||
|
// Convert string ID to number if needed (Payload access functions expect number | undefined)
|
||||||
|
const numericId = typeof options.id === 'string' ? parseInt(options.id, 10) : options.id
|
||||||
const result = await accessFn({
|
const result = await accessFn({
|
||||||
req: request as unknown as PayloadRequest,
|
req: request as unknown as PayloadRequest,
|
||||||
id: options.id,
|
id: numericId,
|
||||||
data: options.data,
|
data: options.data,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ import type { Payload } from 'payload'
|
||||||
import type { Tenant } from '@/payload-types'
|
import type { Tenant } from '@/payload-types'
|
||||||
|
|
||||||
const mockSendMail = vi.fn(async () => ({ messageId: 'mocked-id' }))
|
const mockSendMail = vi.fn(async () => ({ messageId: 'mocked-id' }))
|
||||||
const mockCreateTransport = vi.fn(() => ({ sendMail: mockSendMail }))
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const mockCreateTransport = vi.fn((_options?: unknown) => ({ sendMail: mockSendMail }))
|
||||||
|
|
||||||
vi.mock('nodemailer', () => ({
|
vi.mock('nodemailer', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default: {
|
default: {
|
||||||
createTransport: (...args: unknown[]) => mockCreateTransport(...args),
|
createTransport: (options: unknown) => mockCreateTransport(options),
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,8 +75,13 @@ describe('Payload Localization Integration', () => {
|
||||||
it('payload config has localization enabled', async () => {
|
it('payload config has localization enabled', async () => {
|
||||||
const payloadConfig = await config
|
const payloadConfig = await config
|
||||||
expect(payloadConfig.localization).toBeDefined()
|
expect(payloadConfig.localization).toBeDefined()
|
||||||
expect(payloadConfig.localization?.locales).toBeDefined()
|
expect(payloadConfig.localization).not.toBe(false)
|
||||||
expect(payloadConfig.localization?.defaultLocale).toBe('de')
|
// Type guard for localization config
|
||||||
|
const localization = payloadConfig.localization
|
||||||
|
if (localization && typeof localization === 'object') {
|
||||||
|
expect(localization.locales).toBeDefined()
|
||||||
|
expect(localization.defaultLocale).toBe('de')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('payload config has i18n enabled', async () => {
|
it('payload config has i18n enabled', async () => {
|
||||||
|
|
|
||||||
|
|
@ -242,10 +242,12 @@ describe('Search API Integration', () => {
|
||||||
try {
|
try {
|
||||||
const post = await payload.create({
|
const post = await payload.create({
|
||||||
collection: 'posts',
|
collection: 'posts',
|
||||||
|
draft: false,
|
||||||
data: {
|
data: {
|
||||||
title: 'Searchable Test Post Title',
|
title: 'Searchable Test Post Title',
|
||||||
slug: `searchable-test-post-${Date.now()}`,
|
slug: `searchable-test-post-${Date.now()}`,
|
||||||
excerpt: 'This is a searchable excerpt for testing',
|
excerpt: 'This is a searchable excerpt for testing',
|
||||||
|
type: 'blog',
|
||||||
status: 'published',
|
status: 'published',
|
||||||
publishedAt: new Date().toISOString(),
|
publishedAt: new Date().toISOString(),
|
||||||
tenant: testTenantId,
|
tenant: testTenantId,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||||
import type { Access, PayloadRequest } from 'payload'
|
import type { Access, PayloadRequest, Where } from 'payload'
|
||||||
import {
|
import {
|
||||||
createSuperAdmin,
|
createSuperAdmin,
|
||||||
createTenantUser,
|
createTenantUser,
|
||||||
|
|
@ -122,7 +122,7 @@ describe('EmailLogs Collection Access', () => {
|
||||||
const result = await executeAccess(emailLogsAccess.read, request)
|
const result = await executeAccess(emailLogsAccess.read, request)
|
||||||
|
|
||||||
expect(hasFilteredAccess(result)).toBe(true)
|
expect(hasFilteredAccess(result)).toBe(true)
|
||||||
const tenantIds = getTenantIdsFromInFilter(result as Record<string, unknown>)
|
const tenantIds = getTenantIdsFromInFilter(result as Where)
|
||||||
expect(tenantIds).toContain(1) // porwoll tenant ID
|
expect(tenantIds).toContain(1) // porwoll tenant ID
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -131,7 +131,7 @@ describe('EmailLogs Collection Access', () => {
|
||||||
const result = await executeAccess(emailLogsAccess.read, request)
|
const result = await executeAccess(emailLogsAccess.read, request)
|
||||||
|
|
||||||
expect(hasFilteredAccess(result)).toBe(true)
|
expect(hasFilteredAccess(result)).toBe(true)
|
||||||
const tenantIds = getTenantIdsFromInFilter(result as Record<string, unknown>)
|
const tenantIds = getTenantIdsFromInFilter(result as Where)
|
||||||
expect(tenantIds).toEqual(expect.arrayContaining([1, 4, 5]))
|
expect(tenantIds).toEqual(expect.arrayContaining([1, 4, 5]))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -141,7 +141,7 @@ describe('EmailLogs Collection Access', () => {
|
||||||
const result = await executeAccess(emailLogsAccess.read, request)
|
const result = await executeAccess(emailLogsAccess.read, request)
|
||||||
|
|
||||||
expect(hasFilteredAccess(result)).toBe(true)
|
expect(hasFilteredAccess(result)).toBe(true)
|
||||||
const tenantIds = getTenantIdsFromInFilter(result as Record<string, unknown>)
|
const tenantIds = getTenantIdsFromInFilter(result as Where)
|
||||||
expect(tenantIds).toContain(1)
|
expect(tenantIds).toContain(1)
|
||||||
expect(tenantIds).toContain(4)
|
expect(tenantIds).toContain(4)
|
||||||
})
|
})
|
||||||
|
|
@ -159,7 +159,7 @@ describe('EmailLogs Collection Access', () => {
|
||||||
const result = await executeAccess(emailLogsAccess.read, request)
|
const result = await executeAccess(emailLogsAccess.read, request)
|
||||||
|
|
||||||
expect(hasFilteredAccess(result)).toBe(true)
|
expect(hasFilteredAccess(result)).toBe(true)
|
||||||
const tenantIds = getTenantIdsFromInFilter(result as Record<string, unknown>)
|
const tenantIds = getTenantIdsFromInFilter(result as Where)
|
||||||
expect(tenantIds).toEqual([])
|
expect(tenantIds).toEqual([])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -377,7 +377,7 @@ describe('Access Control Edge Cases', () => {
|
||||||
const result = await executeAccess(emailLogsAccess.read, request)
|
const result = await executeAccess(emailLogsAccess.read, request)
|
||||||
|
|
||||||
expect(hasFilteredAccess(result)).toBe(true)
|
expect(hasFilteredAccess(result)).toBe(true)
|
||||||
const tenantIds = getTenantIdsFromInFilter(result as Record<string, unknown>)
|
const tenantIds = getTenantIdsFromInFilter(result as Where)
|
||||||
expect(tenantIds).toHaveLength(0)
|
expect(tenantIds).toHaveLength(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -428,7 +428,7 @@ describe('Access Control Edge Cases', () => {
|
||||||
const result = await executeAccess(emailLogsAccess.read, request)
|
const result = await executeAccess(emailLogsAccess.read, request)
|
||||||
|
|
||||||
expect(hasFilteredAccess(result)).toBe(true)
|
expect(hasFilteredAccess(result)).toBe(true)
|
||||||
const tenantIds = getTenantIdsFromInFilter(result as Record<string, unknown>)
|
const tenantIds = getTenantIdsFromInFilter(result as Where)
|
||||||
expect(tenantIds.sort()).toEqual([1, 2, 3])
|
expect(tenantIds.sort()).toEqual([1, 2, 3])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||||
import type { PayloadRequest } from 'payload'
|
import type { PayloadRequest, Where } from 'payload'
|
||||||
import {
|
import {
|
||||||
createSuperAdmin,
|
createSuperAdmin,
|
||||||
createTenantUser,
|
createTenantUser,
|
||||||
|
|
@ -173,7 +173,7 @@ describe('tenantScopedPublicRead', () => {
|
||||||
const result = await executeAccess(tenantScopedPublicRead, request)
|
const result = await executeAccess(tenantScopedPublicRead, request)
|
||||||
|
|
||||||
expect(hasFilteredAccess(result)).toBe(true)
|
expect(hasFilteredAccess(result)).toBe(true)
|
||||||
expect(getTenantIdFromFilter(result as Record<string, unknown>)).toBe(1)
|
expect(getTenantIdFromFilter(result as Where)).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns different tenant filter for different domain', async () => {
|
it('returns different tenant filter for different domain', async () => {
|
||||||
|
|
@ -181,7 +181,7 @@ describe('tenantScopedPublicRead', () => {
|
||||||
const result = await executeAccess(tenantScopedPublicRead, request)
|
const result = await executeAccess(tenantScopedPublicRead, request)
|
||||||
|
|
||||||
expect(hasFilteredAccess(result)).toBe(true)
|
expect(hasFilteredAccess(result)).toBe(true)
|
||||||
expect(getTenantIdFromFilter(result as Record<string, unknown>)).toBe(4)
|
expect(getTenantIdFromFilter(result as Where)).toBe(4)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('denies access for unknown domain', async () => {
|
it('denies access for unknown domain', async () => {
|
||||||
|
|
@ -286,7 +286,7 @@ describe('Access Control Integration Scenarios', () => {
|
||||||
|
|
||||||
// Should only see porwoll.de posts
|
// Should only see porwoll.de posts
|
||||||
expect(hasFilteredAccess(result)).toBe(true)
|
expect(hasFilteredAccess(result)).toBe(true)
|
||||||
expect(getTenantIdFromFilter(result as Record<string, unknown>)).toBe(1)
|
expect(getTenantIdFromFilter(result as Where)).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('admin editing posts from any tenant', async () => {
|
it('admin editing posts from any tenant', async () => {
|
||||||
|
|
|
||||||
|
|
@ -288,7 +288,7 @@ describe('Data Masking', () => {
|
||||||
it('handles non-Error objects', () => {
|
it('handles non-Error objects', () => {
|
||||||
const notAnError = { message: 'password=secret', code: 500 }
|
const notAnError = { message: 'password=secret', code: 500 }
|
||||||
|
|
||||||
const masked = maskError(notAnError as Error)
|
const masked = maskError(notAnError as unknown as Error)
|
||||||
|
|
||||||
expect(masked).toBeDefined()
|
expect(masked).toBeDefined()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,7 @@ describe('Rate Limiter', () => {
|
||||||
resetIn: 45000,
|
resetIn: 45000,
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = rateLimitHeaders(result, 30)
|
const headers = rateLimitHeaders(result, 30) as Record<string, string>
|
||||||
|
|
||||||
expect(headers['X-RateLimit-Limit']).toBe('30')
|
expect(headers['X-RateLimit-Limit']).toBe('30')
|
||||||
expect(headers['X-RateLimit-Remaining']).toBe('25')
|
expect(headers['X-RateLimit-Remaining']).toBe('25')
|
||||||
|
|
@ -220,7 +220,7 @@ describe('Rate Limiter', () => {
|
||||||
retryAfter: 30,
|
retryAfter: 30,
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = rateLimitHeaders(result, 10)
|
const headers = rateLimitHeaders(result, 10) as Record<string, string>
|
||||||
|
|
||||||
expect(headers['Retry-After']).toBe('30')
|
expect(headers['Retry-After']).toBe('30')
|
||||||
expect(headers['X-RateLimit-Remaining']).toBe('0')
|
expect(headers['X-RateLimit-Remaining']).toBe('0')
|
||||||
|
|
@ -233,8 +233,8 @@ describe('Rate Limiter', () => {
|
||||||
resetIn: 60000,
|
resetIn: 60000,
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = rateLimitHeaders(result, 10)
|
const headers = rateLimitHeaders(result, 10) as Record<string, string>
|
||||||
const resetValue = headers['X-RateLimit-Reset'] as string
|
const resetValue = headers['X-RateLimit-Reset']
|
||||||
|
|
||||||
// The reset value should be a number (either timestamp or seconds)
|
// The reset value should be a number (either timestamp or seconds)
|
||||||
expect(resetValue).toBeDefined()
|
expect(resetValue).toBeDefined()
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,16 @@
|
||||||
"./src/payload.config.ts"
|
"./src/payload.config.ts"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"target": "ES2022",
|
"target": "ES2022"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"next-env.d.ts",
|
"next-env.d.ts",
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
".next/types/**/*.ts"
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue