diff --git a/next.config.mjs b/next.config.mjs index bbf1b76..74eb6ec 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -16,7 +16,7 @@ const nextConfig = { workerThreads: false, cpus: 1, }, - // Your Next.js config here + // Webpack configuration for TypeScript/ESM compatibility webpack: (webpackConfig) => { webpackConfig.resolve.extensionAlias = { '.cjs': ['.cts', '.cjs'], diff --git a/package.json b/package.json index 2f9c1bd..a779535 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "vitest": "4.0.15" }, "engines": { - "node": "^18.20.2 || >=20.9.0", + "node": ">=20.9.0", "pnpm": "^9 || ^10" }, "pnpm": { diff --git a/tests/helpers/access-control-test-utils.ts b/tests/helpers/access-control-test-utils.ts index 926c312..9db4cbf 100644 --- a/tests/helpers/access-control-test-utils.ts +++ b/tests/helpers/access-control-test-utils.ts @@ -26,8 +26,10 @@ export interface MockTenant { domains?: Array<{ domain: string }> } -export interface MockPayloadRequest extends Partial { +// Note: Not extending PayloadRequest to allow flexible mock types for testing +export interface MockPayloadRequest { user?: MockUser | null + // Allow both Headers and plain object for testing different header formats headers: Headers | Record payload: { find: ReturnType @@ -126,10 +128,10 @@ export function createMockPayloadRequest( tenants?: MockTenant[] } = {}, ): MockPayloadRequest { - const headers: Record = {} + const headers = new Headers() if (options.host) { - headers['host'] = options.host + headers.set('host', options.host) } // Mock payload.find to resolve tenant from host @@ -306,9 +308,11 @@ export async function executeAccess( data?: Record } = {}, ): Promise { + // 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({ req: request as unknown as PayloadRequest, - id: options.id, + id: numericId, data: options.data, }) diff --git a/tests/int/email.int.spec.ts b/tests/int/email.int.spec.ts index a1a1629..0d1e1cc 100644 --- a/tests/int/email.int.spec.ts +++ b/tests/int/email.int.spec.ts @@ -3,12 +3,13 @@ import type { Payload } from 'payload' import type { Tenant } from '@/payload-types' 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', () => ({ __esModule: true, default: { - createTransport: (...args: unknown[]) => mockCreateTransport(...args), + createTransport: (options: unknown) => mockCreateTransport(options), }, })) diff --git a/tests/int/i18n.int.spec.ts b/tests/int/i18n.int.spec.ts index c5591ff..ec8a903 100644 --- a/tests/int/i18n.int.spec.ts +++ b/tests/int/i18n.int.spec.ts @@ -75,8 +75,13 @@ describe('Payload Localization Integration', () => { it('payload config has localization enabled', async () => { const payloadConfig = await config expect(payloadConfig.localization).toBeDefined() - expect(payloadConfig.localization?.locales).toBeDefined() - expect(payloadConfig.localization?.defaultLocale).toBe('de') + expect(payloadConfig.localization).not.toBe(false) + // 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 () => { diff --git a/tests/int/search.int.spec.ts b/tests/int/search.int.spec.ts index 8c991d6..a617033 100644 --- a/tests/int/search.int.spec.ts +++ b/tests/int/search.int.spec.ts @@ -242,10 +242,12 @@ describe('Search API Integration', () => { try { const post = await payload.create({ collection: 'posts', + draft: false, data: { title: 'Searchable Test Post Title', slug: `searchable-test-post-${Date.now()}`, excerpt: 'This is a searchable excerpt for testing', + type: 'blog', status: 'published', publishedAt: new Date().toISOString(), tenant: testTenantId, diff --git a/tests/unit/access-control/collection-access.unit.spec.ts b/tests/unit/access-control/collection-access.unit.spec.ts index 3f6c35f..49d227e 100644 --- a/tests/unit/access-control/collection-access.unit.spec.ts +++ b/tests/unit/access-control/collection-access.unit.spec.ts @@ -9,7 +9,7 @@ */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' -import type { Access, PayloadRequest } from 'payload' +import type { Access, PayloadRequest, Where } from 'payload' import { createSuperAdmin, createTenantUser, @@ -122,7 +122,7 @@ describe('EmailLogs Collection Access', () => { const result = await executeAccess(emailLogsAccess.read, request) expect(hasFilteredAccess(result)).toBe(true) - const tenantIds = getTenantIdsFromInFilter(result as Record) + const tenantIds = getTenantIdsFromInFilter(result as Where) expect(tenantIds).toContain(1) // porwoll tenant ID }) @@ -131,7 +131,7 @@ describe('EmailLogs Collection Access', () => { const result = await executeAccess(emailLogsAccess.read, request) expect(hasFilteredAccess(result)).toBe(true) - const tenantIds = getTenantIdsFromInFilter(result as Record) + const tenantIds = getTenantIdsFromInFilter(result as Where) expect(tenantIds).toEqual(expect.arrayContaining([1, 4, 5])) }) @@ -141,7 +141,7 @@ describe('EmailLogs Collection Access', () => { const result = await executeAccess(emailLogsAccess.read, request) expect(hasFilteredAccess(result)).toBe(true) - const tenantIds = getTenantIdsFromInFilter(result as Record) + const tenantIds = getTenantIdsFromInFilter(result as Where) expect(tenantIds).toContain(1) expect(tenantIds).toContain(4) }) @@ -159,7 +159,7 @@ describe('EmailLogs Collection Access', () => { const result = await executeAccess(emailLogsAccess.read, request) expect(hasFilteredAccess(result)).toBe(true) - const tenantIds = getTenantIdsFromInFilter(result as Record) + const tenantIds = getTenantIdsFromInFilter(result as Where) expect(tenantIds).toEqual([]) }) }) @@ -377,7 +377,7 @@ describe('Access Control Edge Cases', () => { const result = await executeAccess(emailLogsAccess.read, request) expect(hasFilteredAccess(result)).toBe(true) - const tenantIds = getTenantIdsFromInFilter(result as Record) + const tenantIds = getTenantIdsFromInFilter(result as Where) expect(tenantIds).toHaveLength(0) }) @@ -428,7 +428,7 @@ describe('Access Control Edge Cases', () => { const result = await executeAccess(emailLogsAccess.read, request) expect(hasFilteredAccess(result)).toBe(true) - const tenantIds = getTenantIdsFromInFilter(result as Record) + const tenantIds = getTenantIdsFromInFilter(result as Where) expect(tenantIds.sort()).toEqual([1, 2, 3]) }) }) diff --git a/tests/unit/access-control/tenant-access.unit.spec.ts b/tests/unit/access-control/tenant-access.unit.spec.ts index a270be6..3239717 100644 --- a/tests/unit/access-control/tenant-access.unit.spec.ts +++ b/tests/unit/access-control/tenant-access.unit.spec.ts @@ -6,7 +6,7 @@ */ import { describe, it, expect, vi, beforeEach } from 'vitest' -import type { PayloadRequest } from 'payload' +import type { PayloadRequest, Where } from 'payload' import { createSuperAdmin, createTenantUser, @@ -173,7 +173,7 @@ describe('tenantScopedPublicRead', () => { const result = await executeAccess(tenantScopedPublicRead, request) expect(hasFilteredAccess(result)).toBe(true) - expect(getTenantIdFromFilter(result as Record)).toBe(1) + expect(getTenantIdFromFilter(result as Where)).toBe(1) }) it('returns different tenant filter for different domain', async () => { @@ -181,7 +181,7 @@ describe('tenantScopedPublicRead', () => { const result = await executeAccess(tenantScopedPublicRead, request) expect(hasFilteredAccess(result)).toBe(true) - expect(getTenantIdFromFilter(result as Record)).toBe(4) + expect(getTenantIdFromFilter(result as Where)).toBe(4) }) it('denies access for unknown domain', async () => { @@ -286,7 +286,7 @@ describe('Access Control Integration Scenarios', () => { // Should only see porwoll.de posts expect(hasFilteredAccess(result)).toBe(true) - expect(getTenantIdFromFilter(result as Record)).toBe(1) + expect(getTenantIdFromFilter(result as Where)).toBe(1) }) it('admin editing posts from any tenant', async () => { diff --git a/tests/unit/security/data-masking.unit.spec.ts b/tests/unit/security/data-masking.unit.spec.ts index 8ce2c9d..923549f 100644 --- a/tests/unit/security/data-masking.unit.spec.ts +++ b/tests/unit/security/data-masking.unit.spec.ts @@ -288,7 +288,7 @@ describe('Data Masking', () => { it('handles non-Error objects', () => { const notAnError = { message: 'password=secret', code: 500 } - const masked = maskError(notAnError as Error) + const masked = maskError(notAnError as unknown as Error) expect(masked).toBeDefined() }) diff --git a/tests/unit/security/rate-limiter.unit.spec.ts b/tests/unit/security/rate-limiter.unit.spec.ts index d378273..af2bb4e 100644 --- a/tests/unit/security/rate-limiter.unit.spec.ts +++ b/tests/unit/security/rate-limiter.unit.spec.ts @@ -205,7 +205,7 @@ describe('Rate Limiter', () => { resetIn: 45000, } - const headers = rateLimitHeaders(result, 30) + const headers = rateLimitHeaders(result, 30) as Record expect(headers['X-RateLimit-Limit']).toBe('30') expect(headers['X-RateLimit-Remaining']).toBe('25') @@ -220,7 +220,7 @@ describe('Rate Limiter', () => { retryAfter: 30, } - const headers = rateLimitHeaders(result, 10) + const headers = rateLimitHeaders(result, 10) as Record expect(headers['Retry-After']).toBe('30') expect(headers['X-RateLimit-Remaining']).toBe('0') @@ -233,8 +233,8 @@ describe('Rate Limiter', () => { resetIn: 60000, } - const headers = rateLimitHeaders(result, 10) - const resetValue = headers['X-RateLimit-Reset'] as string + const headers = rateLimitHeaders(result, 10) as Record + const resetValue = headers['X-RateLimit-Reset'] // The reset value should be a number (either timestamp or seconds) expect(resetValue).toBeDefined() diff --git a/tsconfig.json b/tsconfig.json index 477c25a..b8592d0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,15 +30,16 @@ "./src/payload.config.ts" ] }, - "target": "ES2022", + "target": "ES2022" }, "include": [ "next-env.d.ts", "**/*.ts", "**/*.tsx", - ".next/types/**/*.ts" + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" ], "exclude": [ "node_modules" - ], + ] }