8aab968e09
Build & Deploy / Build & Push Docker Image (push) Successful in 1m2s
Test / Type Check (all packages) (push) Failing after 28s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Storefront Unit Tests (push) Has been skipped
Test / Admin Unit Tests (push) Has been skipped
Test / Dashboard Unit Tests (push) Has been skipped
Test / API Integration Tests (push) Has been skipped
Build & Deploy / Deploy to VPS (push) Successful in 3s
Comprehensive rename of all marketplace references to storefront: - API module: apps/api/src/modules/marketplace/ → storefront/ - Components: MarketplaceHeader → StorefrontHeader, MarketplaceShell → StorefrontShell, MarketplaceFooter → StorefrontFooter - Types: marketplace-homepage.ts → storefront-homepage.ts - Test files: employee-marketplace-* → employee-storefront-* - All source code identifiers, imports, route paths, and strings - Documentation (docs/), CI config (.gitlab-ci.yml), scripts - Dashboard, admin, storefront workspace references - Prisma field names preserved (isListedOnMarketplace, marketplaceRating, marketplaceFunnelEvent) as they map to database schema Validation: - API type-check: 0 errors - Storefront type-check: 0 errors - Dashboard type-check: 0 errors - Full monorepo type-check: only pre-existing admin TS18046
147 lines
5.2 KiB
TypeScript
147 lines
5.2 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const nextServer = vi.hoisted(() => {
|
|
const next = vi.fn((init?: { request?: { headers?: Headers } }) => ({
|
|
kind: 'next',
|
|
init,
|
|
cookies: {
|
|
set: vi.fn(),
|
|
},
|
|
}))
|
|
const redirect = vi.fn((url: URL, status?: number) => ({
|
|
kind: 'redirect',
|
|
url: url.toString(),
|
|
status,
|
|
}))
|
|
const MockNextResponse = vi.fn((body?: string, init?: { status?: number }) => ({
|
|
kind: 'response',
|
|
body,
|
|
status: init?.status,
|
|
})) as any
|
|
MockNextResponse.next = next
|
|
MockNextResponse.redirect = redirect
|
|
return { next, redirect, MockNextResponse }
|
|
})
|
|
|
|
vi.mock('next/server', () => ({
|
|
NextResponse: nextServer.MockNextResponse,
|
|
}))
|
|
|
|
function request(options: { cookies?: Record<string, string>; headers?: Record<string, string> } = {}) {
|
|
const cookies = new Map(Object.entries(options.cookies ?? {}))
|
|
const headers = new Headers(options.headers ?? {})
|
|
return {
|
|
nextUrl: cloneableUrl('http://localhost:3000/'),
|
|
cookies: {
|
|
get: vi.fn((name: string) => {
|
|
const value = cookies.get(name)
|
|
return value ? { value } : undefined
|
|
}),
|
|
},
|
|
headers,
|
|
}
|
|
}
|
|
|
|
function cloneableUrl(input: string): URL {
|
|
const url = new URL(input)
|
|
;(url as URL & { clone: () => URL }).clone = () => cloneableUrl(url.toString())
|
|
return url
|
|
}
|
|
|
|
function middlewareRequest(input: string) {
|
|
return {
|
|
cookies: {
|
|
get: vi.fn(),
|
|
},
|
|
headers: new Headers(),
|
|
nextUrl: cloneableUrl(input),
|
|
}
|
|
}
|
|
|
|
beforeEach(() => {
|
|
nextServer.next.mockClear()
|
|
nextServer.redirect.mockClear()
|
|
})
|
|
|
|
describe('storefront proxy language bootstrap', () => {
|
|
|
|
it('rejects proxy subrequest headers before language handling', async () => {
|
|
const { proxy } = await import('./proxy')
|
|
|
|
const response = proxy(request({
|
|
headers: { 'x-middleware-subrequest': 'middleware:middleware' },
|
|
}) as never) as any
|
|
|
|
expect(response).toEqual({ kind: 'response', body: 'Unsupported internal request header', status: 400 })
|
|
expect(nextServer.next).not.toHaveBeenCalled()
|
|
})
|
|
it('does nothing when the canonical shared language cookie is valid', async () => {
|
|
const { proxy } = await import('./proxy')
|
|
|
|
const response = proxy(request({ cookies: { 'rentaldrivego-language': 'fr' } }) as never) as any
|
|
|
|
expect(response.kind).toBe('next')
|
|
expect(nextServer.next).toHaveBeenCalledWith()
|
|
expect(response.cookies.set).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('migrates the legacy storefront language cookie into the shared cookie and request headers', async () => {
|
|
const { proxy } = await import('./proxy')
|
|
|
|
const response = proxy(request({
|
|
cookies: { 'storefront-language': 'ar' },
|
|
headers: { cookie: 'session=abc' },
|
|
}) as never) as any
|
|
|
|
expect(response.cookies.set).toHaveBeenCalledWith('rentaldrivego-language', 'ar', expect.objectContaining({
|
|
path: '/',
|
|
sameSite: 'lax',
|
|
}))
|
|
expect(response.init?.request?.headers?.get('cookie')).toBe('session=abc; rentaldrivego-language=ar')
|
|
})
|
|
|
|
it('detects Arabic and French from Accept-Language before falling back to English', async () => {
|
|
const { proxy } = await import('./proxy')
|
|
|
|
const arabic = proxy(request({ headers: { 'accept-language': 'ar-MA,fr;q=0.8,en;q=0.6' } }) as never) as any
|
|
expect(arabic.cookies.set).toHaveBeenCalledWith('rentaldrivego-language', 'ar', expect.any(Object))
|
|
expect(arabic.init?.request?.headers?.get('cookie')).toBe('rentaldrivego-language=ar')
|
|
|
|
const french = proxy(request({ headers: { 'accept-language': 'de-DE,fr-FR;q=0.7,en;q=0.3' } }) as never) as any
|
|
expect(french.cookies.set).toHaveBeenCalledWith('rentaldrivego-language', 'fr', expect.any(Object))
|
|
|
|
const fallback = proxy(request({ headers: { 'accept-language': 'de-DE,es;q=0.9' } }) as never) as any
|
|
expect(fallback.cookies.set).toHaveBeenCalledWith('rentaldrivego-language', 'en', expect.any(Object))
|
|
})
|
|
|
|
it('rejects invalid shared and legacy language values before resolving from headers', async () => {
|
|
const { proxy } = await import('./proxy')
|
|
|
|
const response = proxy(request({
|
|
cookies: { 'rentaldrivego-language': 'es', 'storefront-language': 'it' },
|
|
headers: { 'accept-language': 'fr-CA,ar;q=0.5' },
|
|
}) as never) as any
|
|
|
|
expect(response.cookies.set).toHaveBeenCalledWith('rentaldrivego-language', 'fr', expect.any(Object))
|
|
expect(response.init?.request?.headers?.get('cookie')).toBe('rentaldrivego-language=fr')
|
|
})
|
|
})
|
|
|
|
describe('storefront middleware dashboard path canonicalization', () => {
|
|
it('redirects duplicate dashboard prefixes to the clean dashboard URL', async () => {
|
|
const { proxy } = await import('./proxy')
|
|
|
|
const response = proxy(middlewareRequest('http://localhost:3000/dashboard/dashboard') as never)
|
|
|
|
expect(response).toEqual({ kind: 'redirect', url: 'http://localhost:3000/dashboard', status: 307 })
|
|
})
|
|
|
|
it('preserves nested dashboard paths while removing the duplicate prefix', async () => {
|
|
const { proxy } = await import('./proxy')
|
|
|
|
const response = proxy(middlewareRequest('http://localhost:3000/dashboard/dashboard/fleet?tab=active') as never)
|
|
|
|
expect(response).toEqual({ kind: 'redirect', url: 'http://localhost:3000/dashboard/fleet?tab=active', status: 307 })
|
|
})
|
|
})
|