From ac2672729fce6ec5a4bdd829a9ac98524044a113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Fri, 13 Mar 2026 18:55:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E7=AE=A1=E7=90=86API=E5=8F=8A=E7=9B=B8=E5=85=B3=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor(services-section): 重构服务展示组件使用API数据 refactor(news-section): 重构新闻展示组件使用API数据 refactor(products-section): 重构产品展示组件使用API数据 test: 添加API客户端和服务钩子的单元测试 test(e2e): 添加配置验证和API响应格式的端到端测试 ci: 更新Playwright测试配置 --- .auth/admin.json | 55 ++ e2e/playwright.config.tiered.ts | 3 +- e2e/src/tests/unit/api-response.spec.ts | 391 +++++++++++++ e2e/src/tests/unit/config-transform.spec.ts | 404 ++++++++++++++ e2e/src/tests/unit/config-validation.spec.ts | 300 ++++++++++ src/app/api/content/route.ts | 54 ++ .../news-section.integration.test.tsx | 513 ++++++++++++++++++ src/components/sections/news-section.tsx | 117 ++-- .../products-section.integration.test.tsx | 472 ++++++++++++++++ src/components/sections/products-section.tsx | 184 ++++--- .../services-section.integration.test.tsx | 347 ++++++++++++ src/components/sections/services-section.tsx | 104 ++-- src/hooks/use-news.ts | 32 ++ src/hooks/use-products.ts | 28 + src/hooks/use-services.ts | 28 + src/lib/api/client.test.ts | 211 +++++++ src/lib/api/client.ts | 221 ++++++++ src/lib/api/services.test.ts | 426 +++++++++++++++ src/lib/api/services.ts | 131 +++++ src/lib/api/types.ts | 66 +++ 20 files changed, 3934 insertions(+), 153 deletions(-) create mode 100644 .auth/admin.json create mode 100644 e2e/src/tests/unit/api-response.spec.ts create mode 100644 e2e/src/tests/unit/config-transform.spec.ts create mode 100644 e2e/src/tests/unit/config-validation.spec.ts create mode 100644 src/app/api/content/route.ts create mode 100644 src/components/sections/news-section.integration.test.tsx create mode 100644 src/components/sections/products-section.integration.test.tsx create mode 100644 src/components/sections/services-section.integration.test.tsx create mode 100644 src/hooks/use-news.ts create mode 100644 src/hooks/use-products.ts create mode 100644 src/hooks/use-services.ts create mode 100644 src/lib/api/client.test.ts create mode 100644 src/lib/api/client.ts create mode 100644 src/lib/api/services.test.ts create mode 100644 src/lib/api/services.ts create mode 100644 src/lib/api/types.ts diff --git a/.auth/admin.json b/.auth/admin.json new file mode 100644 index 0000000..7c99002 --- /dev/null +++ b/.auth/admin.json @@ -0,0 +1,55 @@ +{ + "cookies": [ + { + "name": "authjs.csrf-token", + "value": "2940c862b3fcc58543ae12377761900f7c74eb4a910054b9d7a082e08ac7eec7%7Cd3df7b4b17680c02c9105d10e6df7cb22ccb7656cf7f384a7272b6d6a28287a6", + "domain": "localhost", + "path": "/", + "expires": -1, + "httpOnly": true, + "secure": false, + "sameSite": "Lax" + }, + { + "name": "_ga", + "value": "GA1.1.883952371.1773396051", + "domain": "localhost", + "path": "/", + "expires": 1807956050.784813, + "httpOnly": false, + "secure": false, + "sameSite": "Lax" + }, + { + "name": "_ga_LGTLCR15KM", + "value": "GS2.1.s1773396050$o1$g0$t1773396053$j57$l0$h0", + "domain": "localhost", + "path": "/", + "expires": 1807956053.726289, + "httpOnly": false, + "secure": false, + "sameSite": "Lax" + }, + { + "name": "authjs.callback-url", + "value": "http%3A%2F%2Flocalhost%3A3000%2Fadmin%2Flogin", + "domain": "localhost", + "path": "/", + "expires": -1, + "httpOnly": true, + "secure": false, + "sameSite": "Lax" + }, + { + "name": "authjs.session-token", + "value": "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwia2lkIjoiUkJOSmVSR3M5QlFaQnhQbkVHMG9MSy1ZaHY0YmVqM3JuZjJ4eHN1NzNLaHhhVlJQamVtVU1RT2M1WlJPY085ckZPbG1KbFk5MTRNUV9pV3BGVzV1VWcifQ..8ahF3Wx9hRRJJ8NNTHBagQ.Yk7DHEdEHhV36PTvzZEW8d3r6NfBkRQzqCY4ADGKTpdkTAIMT93JKYq2x90oyFqIH9O2dM9CxBkywgbKRdtm3jrxMQuh_9sg7KwIGRnhjkOBRNNRY6ov0y64WGFYy7b0Nf-CevQIva56GIEqIUMyfGcMRfNe-hJNUXdQ40xNJ_TYTenNYWxUhRYVoM4zSCIABVJHk2nY1lGdxaGNq48S2FqjCFzsTEC2DXZ_KFjGks_modve2K7Y1PUzUg0KYZERL209JIJHfMqimgewuxGI0w.xf03-8g2fEU4v0aoCuu5Q0caDatzWoxtxctL7vL8hCU", + "domain": "localhost", + "path": "/", + "expires": 1775988053.888913, + "httpOnly": true, + "secure": false, + "sameSite": "Lax" + } + ], + "origins": [] +} \ No newline at end of file diff --git a/e2e/playwright.config.tiered.ts b/e2e/playwright.config.tiered.ts index e7508e3..e8fc734 100644 --- a/e2e/playwright.config.tiered.ts +++ b/e2e/playwright.config.tiered.ts @@ -1,8 +1,7 @@ import { defineConfig, devices } from '@playwright/test'; import { getEnvironment } from './src/config/environments'; import { getMobileDevices } from './src/utils/devices'; -import { getTestTier, TEST_TIERS } from './src/config/test-tiers'; -import globalSetup from './global-setup'; +import { getTestTier } from './src/config/test-tiers'; const env = getEnvironment(); diff --git a/e2e/src/tests/unit/api-response.spec.ts b/e2e/src/tests/unit/api-response.spec.ts new file mode 100644 index 0000000..2ab23dd --- /dev/null +++ b/e2e/src/tests/unit/api-response.spec.ts @@ -0,0 +1,391 @@ +import { describe, test, expect } from '@playwright/test'; + +describe('API响应格式测试', () => { + describe('Success响应格式', () => { + test('验证success响应结构', () => { + const successResponse = { + success: true, + data: { key: 'value' } + }; + + expect(successResponse).toHaveProperty('success'); + expect(successResponse).toHaveProperty('data'); + expect(successResponse.success).toBe(true); + expect(typeof successResponse.data).toBe('object'); + }); + + test('验证success响应带configs数组', () => { + const successResponse = { + success: true, + configs: [ + { id: '1', key: 'feature_services', value: {} }, + { id: '2', key: 'feature_products', value: {} } + ] + }; + + expect(successResponse).toHaveProperty('success'); + expect(successResponse).toHaveProperty('configs'); + expect(successResponse.success).toBe(true); + expect(Array.isArray(successResponse.configs)).toBe(true); + expect(successResponse.configs).toHaveLength(2); + }); + + test('验证success响应带单个config', () => { + const successResponse = { + success: true, + configs: [ + { id: '1', key: 'feature_services', value: {} } + ] + }; + + expect(successResponse.success).toBe(true); + expect(successResponse.configs).toHaveLength(1); + expect(successResponse.configs[0].key).toBe('feature_services'); + }); + + test('验证success响应状态码', () => { + const statusCodes = [200, 201, 204]; + + statusCodes.forEach(statusCode => { + expect(statusCode).toBeGreaterThanOrEqual(200); + expect(statusCode).toBeLessThan(300); + }); + }); + }); + + describe('Error响应格式', () => { + test('验证error响应结构', () => { + const errorResponse = { + success: false, + error: '错误信息', + code: 'VALIDATION_ERROR' + }; + + expect(errorResponse).toHaveProperty('success'); + expect(errorResponse).toHaveProperty('error'); + expect(errorResponse).toHaveProperty('code'); + expect(errorResponse.success).toBe(false); + expect(typeof errorResponse.error).toBe('string'); + expect(typeof errorResponse.code).toBe('string'); + }); + + test('验证error响应带details', () => { + const errorResponse = { + success: false, + error: '数据验证失败', + code: 'VALIDATION_ERROR', + details: { + field: 'key', + message: 'key字段不能为空' + } + }; + + expect(errorResponse.success).toBe(false); + expect(errorResponse).toHaveProperty('details'); + expect(typeof errorResponse.details).toBe('object'); + expect(errorResponse.details.field).toBe('key'); + }); + + test('验证UNAUTHORIZED错误响应', () => { + const errorResponse = { + success: false, + error: '未授权,请先登录', + code: 'UNAUTHORIZED' + }; + + expect(errorResponse.success).toBe(false); + expect(errorResponse.code).toBe('UNAUTHORIZED'); + expect(errorResponse.error).toContain('未授权'); + }); + + test('验证FORBIDDEN错误响应', () => { + const errorResponse = { + success: false, + error: '无权限执行此操作', + code: 'FORBIDDEN' + }; + + expect(errorResponse.success).toBe(false); + expect(errorResponse.code).toBe('FORBIDDEN'); + expect(errorResponse.error).toContain('无权限'); + }); + + test('验证NOT_FOUND错误响应', () => { + const errorResponse = { + success: false, + error: '请求的资源不存在', + code: 'NOT_FOUND' + }; + + expect(errorResponse.success).toBe(false); + expect(errorResponse.code).toBe('NOT_FOUND'); + expect(errorResponse.error).toContain('不存在'); + }); + + test('验证VALIDATION_ERROR错误响应', () => { + const errorResponse = { + success: false, + error: '数据验证失败', + code: 'VALIDATION_ERROR' + }; + + expect(errorResponse.success).toBe(false); + expect(errorResponse.code).toBe('VALIDATION_ERROR'); + expect(errorResponse.error).toContain('验证'); + }); + + test('验证INTERNAL_ERROR错误响应', () => { + const errorResponse = { + success: false, + error: '服务器内部错误', + code: 'INTERNAL_ERROR' + }; + + expect(errorResponse.success).toBe(false); + expect(errorResponse.code).toBe('INTERNAL_ERROR'); + expect(errorResponse.error).toContain('服务器'); + }); + + test('验证BAD_REQUEST错误响应', () => { + const errorResponse = { + success: false, + error: '请求参数错误', + code: 'BAD_REQUEST' + }; + + expect(errorResponse.success).toBe(false); + expect(errorResponse.code).toBe('BAD_REQUEST'); + expect(errorResponse.error).toContain('参数'); + }); + + test('验证error响应状态码', () => { + const errorCodes = { + 'UNAUTHORIZED': 401, + 'FORBIDDEN': 403, + 'NOT_FOUND': 404, + 'VALIDATION_ERROR': 400, + 'INTERNAL_ERROR': 500, + 'BAD_REQUEST': 400 + }; + + Object.entries(errorCodes).forEach(([code, statusCode]) => { + expect(statusCode).toBeGreaterThanOrEqual(400); + expect(statusCode).toBeLessThan(600); + }); + }); + }); + + describe('配置API响应格式', () => { + test('验证GET /api/admin/config响应格式', () => { + const response = { + success: true, + configs: [ + { + id: '1', + key: 'feature_services', + value: { enabled: true, items: ['erp', 'crm'] }, + category: 'feature', + description: '服务功能配置', + updatedAt: '2024-01-01T00:00:00.000Z', + updatedBy: 'admin' + } + ] + }; + + expect(response.success).toBe(true); + expect(Array.isArray(response.configs)).toBe(true); + expect(response.configs[0]).toHaveProperty('id'); + expect(response.configs[0]).toHaveProperty('key'); + expect(response.configs[0]).toHaveProperty('value'); + expect(response.configs[0]).toHaveProperty('category'); + expect(response.configs[0]).toHaveProperty('description'); + expect(response.configs[0]).toHaveProperty('updatedAt'); + expect(response.configs[0]).toHaveProperty('updatedBy'); + }); + + test('验证POST /api/admin/config响应格式', () => { + const response = { + success: true, + configs: [ + { + id: '1', + key: 'feature_services', + value: { enabled: true, items: ['erp'] }, + category: 'feature', + description: '服务功能配置', + updatedAt: '2024-01-01T00:00:00.000Z', + updatedBy: 'admin' + } + ] + }; + + expect(response.success).toBe(true); + expect(response.configs).toHaveLength(1); + expect(response.configs[0].key).toBe('feature_services'); + }); + + test('验证PUT /api/admin/config响应格式', () => { + const response = { + success: true, + configs: [ + { id: '1', key: 'feature_services', value: { enabled: false } }, + { id: '2', key: 'feature_products', value: { enabled: true } } + ] + }; + + expect(response.success).toBe(true); + expect(response.configs).toHaveLength(2); + expect(response.configs[0].value.enabled).toBe(false); + expect(response.configs[1].value.enabled).toBe(true); + }); + + test('验证DELETE /api/admin/config响应格式', () => { + const response = { + success: true, + data: { + success: true, + message: '配置删除成功' + } + }; + + expect(response.success).toBe(true); + expect(response.data).toHaveProperty('success'); + expect(response.data.success).toBe(true); + expect(response.data).toHaveProperty('message'); + }); + + test('验证GET /api/config响应格式', () => { + const response = { + success: true, + data: { + feature_services: { enabled: true, items: ['erp', 'crm'] }, + feature_products: { enabled: false, showPricing: true, featuredProducts: [] }, + feature_news: { enabled: true, displayCount: 5, categories: ['tech'], sortOrder: 'desc' } + } + }; + + expect(response.success).toBe(true); + expect(response.data).toHaveProperty('feature_services'); + expect(response.data).toHaveProperty('feature_products'); + expect(response.data).toHaveProperty('feature_news'); + expect(typeof response.data.feature_services).toBe('object'); + expect(typeof response.data.feature_products).toBe('object'); + expect(typeof response.data.feature_news).toBe('object'); + }); + }); + + describe('响应数据类型验证', () => { + test('验证配置值类型', () => { + const configValue = { + enabled: true, + count: 5, + items: ['erp', 'crm'], + metadata: { key: 'value' } + }; + + expect(typeof configValue.enabled).toBe('boolean'); + expect(typeof configValue.count).toBe('number'); + expect(Array.isArray(configValue.items)).toBe(true); + expect(typeof configValue.metadata).toBe('object'); + }); + + test('验证日期格式', () => { + const dateFormats = [ + '2024-01-01T00:00:00.000Z', + '2024-12-31T23:59:59.999Z', + '2024-06-15T12:30:45.123Z' + ]; + + dateFormats.forEach(dateString => { + const date = new Date(dateString); + expect(date.getTime()).not.toBeNaN(); + }); + }); + + test('验证ID格式', () => { + const ids = ['1', '2', '3', 'admin', 'user_123']; + + ids.forEach(id => { + expect(typeof id).toBe('string'); + expect(id.length).toBeGreaterThan(0); + }); + }); + + test('验证枚举值格式', () => { + const categories = ['feature', 'style', 'seo', 'general']; + const sortOrders = ['asc', 'desc']; + + categories.forEach(category => { + expect(typeof category).toBe('string'); + expect(categories.includes(category)).toBe(true); + }); + + sortOrders.forEach(order => { + expect(typeof order).toBe('string'); + expect(sortOrders.includes(order)).toBe(true); + }); + }); + }); + + describe('响应边界情况', () => { + test('验证空数组响应', () => { + const response = { + success: true, + configs: [] + }; + + expect(response.success).toBe(true); + expect(Array.isArray(response.configs)).toBe(true); + expect(response.configs).toHaveLength(0); + }); + + test('验证空对象响应', () => { + const response = { + success: true, + data: {} + }; + + expect(response.success).toBe(true); + expect(typeof response.data).toBe('object'); + expect(Object.keys(response.data)).toHaveLength(0); + }); + + test('验证null值处理', () => { + const response = { + success: true, + configs: [ + { + id: '1', + key: 'feature_services', + value: { enabled: true, items: [] }, + category: 'feature', + description: null, + updatedAt: '2024-01-01T00:00:00.000Z', + updatedBy: null + } + ] + }; + + expect(response.success).toBe(true); + expect(response.configs[0].description).toBe(null); + expect(response.configs[0].updatedBy).toBe(null); + }); + + test('验证undefined值处理', () => { + const response = { + success: true, + configs: [ + { + id: '1', + key: 'feature_services', + value: { enabled: true } + } + ] + }; + + expect(response.success).toBe(true); + expect(response.configs[0].description).toBeUndefined(); + expect(response.configs[0].updatedBy).toBeUndefined(); + }); + }); +}); diff --git a/e2e/src/tests/unit/config-transform.spec.ts b/e2e/src/tests/unit/config-transform.spec.ts new file mode 100644 index 0000000..41f6c6c --- /dev/null +++ b/e2e/src/tests/unit/config-transform.spec.ts @@ -0,0 +1,404 @@ +import { describe, test, expect } from '@playwright/test'; + +describe('配置转换函数测试', () => { + describe('数据库格式到API格式转换', () => { + test('转换单个配置对象', () => { + const dbConfig = { + id: '1', + key: 'feature_services', + value: JSON.stringify({ enabled: true, items: ['erp', 'crm'] }), + category: 'feature', + description: '服务功能配置', + updatedAt: new Date(), + updatedBy: 'admin' + }; + + const apiConfig = { + id: dbConfig.id, + key: dbConfig.key, + value: JSON.parse(dbConfig.value), + category: dbConfig.category, + description: dbConfig.description, + updatedAt: dbConfig.updatedAt, + updatedBy: dbConfig.updatedBy + }; + + expect(apiConfig.key).toBe('feature_services'); + expect(apiConfig.value.enabled).toBe(true); + expect(apiConfig.value.items).toEqual(['erp', 'crm']); + expect(apiConfig.category).toBe('feature'); + }); + + test('转换配置数组', () => { + const dbConfigs = [ + { + id: '1', + key: 'feature_services', + value: JSON.stringify({ enabled: true, items: ['erp'] }), + category: 'feature', + description: null, + updatedAt: new Date(), + updatedBy: 'admin' + }, + { + id: '2', + key: 'feature_products', + value: JSON.stringify({ enabled: false, showPricing: true, featuredProducts: [] }), + category: 'feature', + description: null, + updatedAt: new Date(), + updatedBy: 'admin' + } + ]; + + const apiConfigs = dbConfigs.map(config => ({ + id: config.id, + key: config.key, + value: JSON.parse(config.value), + category: config.category, + description: config.description, + updatedAt: config.updatedAt, + updatedBy: config.updatedBy + })); + + expect(apiConfigs).toHaveLength(2); + expect(apiConfigs[0].key).toBe('feature_services'); + expect(apiConfigs[1].key).toBe('feature_products'); + expect(apiConfigs[0].value.enabled).toBe(true); + expect(apiConfigs[1].value.enabled).toBe(false); + }); + + test('处理无效JSON值', () => { + const dbConfig = { + id: '1', + key: 'feature_services', + value: 'invalid json', + category: 'feature', + description: null, + updatedAt: new Date(), + updatedBy: 'admin' + }; + + const parseConfig = (config: any) => { + try { + return JSON.parse(config.value); + } catch { + return null; + } + }; + + const result = parseConfig(dbConfig); + expect(result).toBe(null); + }); + + test('处理null值', () => { + const dbConfig = { + id: '1', + key: 'feature_services', + value: null, + category: 'feature', + description: null, + updatedAt: new Date(), + updatedBy: 'admin' + }; + + const parseConfig = (config: any) => { + try { + return JSON.parse(config.value); + } catch { + return null; + } + }; + + const result = parseConfig(dbConfig); + expect(result).toBe(null); + }); + }); + + describe('API格式到数据库格式转换', () => { + test('转换单个配置对象', () => { + const apiConfig = { + key: 'feature_services', + value: { enabled: true, items: ['erp', 'crm'] }, + category: 'feature', + description: '服务功能配置' + }; + + const dbConfig = { + id: '1', + key: apiConfig.key, + value: JSON.stringify(apiConfig.value), + category: apiConfig.category, + description: apiConfig.description, + updatedAt: new Date(), + updatedBy: 'admin' + }; + + expect(dbConfig.key).toBe('feature_services'); + expect(JSON.parse(dbConfig.value)).toEqual({ enabled: true, items: ['erp', 'crm'] }); + expect(dbConfig.category).toBe('feature'); + }); + + test('转换配置数组', () => { + const apiConfigs = [ + { key: 'feature_services', value: { enabled: true, items: ['erp'] }, category: 'feature' }, + { key: 'feature_products', value: { enabled: false, showPricing: true, featuredProducts: [] }, category: 'feature' } + ]; + + const dbConfigs = apiConfigs.map((config, index) => ({ + id: String(index + 1), + key: config.key, + value: JSON.stringify(config.value), + category: config.category, + description: null, + updatedAt: new Date(), + updatedBy: 'admin' + })); + + expect(dbConfigs).toHaveLength(2); + expect(dbConfigs[0].key).toBe('feature_services'); + expect(dbConfigs[1].key).toBe('feature_products'); + expect(JSON.parse(dbConfigs[0].value)).toEqual({ enabled: true, items: ['erp'] }); + }); + + test('处理复杂嵌套对象', () => { + const apiConfig = { + key: 'feature_news', + value: { + enabled: true, + displayCount: 5, + categories: ['tech', 'business'], + sortOrder: 'desc', + metadata: { + author: 'admin', + tags: ['news', 'updates'] + } + }, + category: 'feature' + }; + + const dbConfig = { + id: '1', + key: apiConfig.key, + value: JSON.stringify(apiConfig.value), + category: apiConfig.category, + description: null, + updatedAt: new Date(), + updatedBy: 'admin' + }; + + const parsedValue = JSON.parse(dbConfig.value); + expect(parsedValue.enabled).toBe(true); + expect(parsedValue.displayCount).toBe(5); + expect(parsedValue.categories).toEqual(['tech', 'business']); + expect(parsedValue.sortOrder).toBe('desc'); + expect(parsedValue.metadata.author).toBe('admin'); + expect(parsedValue.metadata.tags).toEqual(['news', 'updates']); + }); + }); + + describe('配置分组和过滤', () => { + test('按分类分组配置', () => { + const configs = [ + { key: 'feature_services', category: 'feature', value: {} }, + { key: 'feature_products', category: 'feature', value: {} }, + { key: 'style_colors', category: 'style', value: {} }, + { key: 'seo_title', category: 'seo', value: {} }, + { key: 'general_language', category: 'general', value: {} } + ]; + + const groupedConfigs = configs.reduce((acc, config) => { + if (!acc[config.category]) { + acc[config.category] = []; + } + acc[config.category].push(config); + return acc; + }, {} as Record); + + expect(groupedConfigs.feature).toHaveLength(2); + expect(groupedConfigs.style).toHaveLength(1); + expect(groupedConfigs.seo).toHaveLength(1); + expect(groupedConfigs.general).toHaveLength(1); + }); + + test('按key过滤配置', () => { + const configs = [ + { key: 'feature_services', category: 'feature', value: {} }, + { key: 'feature_products', category: 'feature', value: {} }, + { key: 'feature_news', category: 'feature', value: {} } + ]; + + const filterByKey = (configs: any[], key: string) => { + return configs.filter(config => config.key === key); + }; + + const result = filterByKey(configs, 'feature_products'); + expect(result).toHaveLength(1); + expect(result[0].key).toBe('feature_products'); + }); + + test('按分类过滤配置', () => { + const configs = [ + { key: 'feature_services', category: 'feature', value: {} }, + { key: 'style_colors', category: 'style', value: {} }, + { key: 'seo_title', category: 'seo', value: {} }, + { key: 'feature_products', category: 'feature', value: {} } + ]; + + const filterByCategory = (configs: any[], category: string) => { + return configs.filter(config => config.category === category); + }; + + const result = filterByCategory(configs, 'feature'); + expect(result).toHaveLength(2); + expect(result.every(config => config.category === 'feature')).toBe(true); + }); + + test('按key前缀过滤配置', () => { + const configs = [ + { key: 'feature_services', category: 'feature', value: {} }, + { key: 'feature_products', category: 'feature', value: {} }, + { key: 'feature_news', category: 'feature', value: {} }, + { key: 'style_colors', category: 'style', value: {} }, + { key: 'seo_title', category: 'seo', value: {} } + ]; + + const filterByKeyPrefix = (configs: any[], prefix: string) => { + return configs.filter(config => config.key.startsWith(prefix)); + }; + + const result = filterByKeyPrefix(configs, 'feature_'); + expect(result).toHaveLength(3); + expect(result.every(config => config.key.startsWith('feature_'))).toBe(true); + }); + }); + + describe('配置合并和更新', () => { + test('合并配置对象', () => { + const baseConfig = { + enabled: true, + items: ['erp', 'crm'] + }; + + const updateConfig = { + enabled: false, + count: 5 + }; + + const mergedConfig = { ...baseConfig, ...updateConfig }; + + expect(mergedConfig.enabled).toBe(false); + expect(mergedConfig.items).toEqual(['erp', 'crm']); + expect(mergedConfig.count).toBe(5); + }); + + test('深度合并配置对象', () => { + const baseConfig = { + enabled: true, + metadata: { + author: 'admin', + tags: ['tag1'] + } + }; + + const updateConfig = { + metadata: { + tags: ['tag1', 'tag2'] + } + }; + + const deepMerge = (target: any, source: any) => { + const output = { ...target }; + for (const key in source) { + if (source[key] instanceof Object && !Array.isArray(source[key]) && key in target) { + output[key] = deepMerge(target[key], source[key]); + } else { + output[key] = source[key]; + } + } + return output; + }; + + const mergedConfig = deepMerge(baseConfig, updateConfig); + + expect(mergedConfig.enabled).toBe(true); + expect(mergedConfig.metadata.author).toBe('admin'); + expect(mergedConfig.metadata.tags).toEqual(['tag1', 'tag2']); + }); + + test('批量更新配置', () => { + const configs = [ + { key: 'feature_services', value: { enabled: true, items: ['erp'] } }, + { key: 'feature_products', value: { enabled: false, showPricing: true, featuredProducts: [] } } + ]; + + const updates = [ + { key: 'feature_services', value: { enabled: false } }, + { key: 'feature_products', value: { enabled: true, featuredProducts: ['product1'] } } + ]; + + const updatedConfigs = configs.map(config => { + const update = updates.find(u => u.key === config.key); + if (update) { + return { + ...config, + value: { ...config.value, ...update.value } + }; + } + return config; + }); + + expect(updatedConfigs[0].value.enabled).toBe(false); + expect(updatedConfigs[0].value.items).toEqual(['erp']); + expect(updatedConfigs[1].value.enabled).toBe(true); + expect(updatedConfigs[1].value.featuredProducts).toEqual(['product1']); + }); + }); + + describe('配置序列化和反序列化', () => { + test('序列化配置为JSON', () => { + const config = { + key: 'feature_services', + value: { enabled: true, items: ['erp', 'crm'] }, + category: 'feature' + }; + + const serialized = JSON.stringify(config); + const deserialized = JSON.parse(serialized); + + expect(deserialized.key).toBe('feature_services'); + expect(deserialized.value.enabled).toBe(true); + expect(deserialized.value.items).toEqual(['erp', 'crm']); + }); + + test('处理日期序列化', () => { + const config = { + key: 'feature_services', + value: { enabled: true }, + category: 'feature', + updatedAt: new Date('2024-01-01T00:00:00.000Z') + }; + + const serialized = JSON.stringify(config); + const deserialized = JSON.parse(serialized); + + expect(typeof deserialized.updatedAt).toBe('string'); + expect(new Date(deserialized.updatedAt).toISOString()).toBe(config.updatedAt.toISOString()); + }); + + test('处理特殊字符序列化', () => { + const config = { + key: 'feature_services', + value: { items: ['erp', 'crm', 'mes'] }, + category: 'feature', + description: '服务配置 ' + }; + + const serialized = JSON.stringify(config); + const deserialized = JSON.parse(serialized); + + expect(deserialized.description).toContain('