|
| 1 | +import { describe, it, expect } from 'vitest'; |
| 2 | +import { |
| 3 | + DatabaseProviderSchema, |
| 4 | + DatabasePoolConfigSchema, |
| 5 | + SslConfigSchema, |
| 6 | + CdcConfigSchema, |
| 7 | + DatabaseTableSchema, |
| 8 | + DatabaseConnectorSchema, |
| 9 | +} from './database.zod'; |
| 10 | + |
| 11 | +const baseAuth = { type: 'none' as const }; |
| 12 | + |
| 13 | +const minimalTable = { |
| 14 | + name: 'customer', |
| 15 | + label: 'Customer', |
| 16 | + tableName: 'customers', |
| 17 | + primaryKey: 'id', |
| 18 | +}; |
| 19 | + |
| 20 | +const minimalConnector = { |
| 21 | + name: 'pg_main', |
| 22 | + label: 'PostgreSQL Main', |
| 23 | + type: 'database' as const, |
| 24 | + provider: 'postgresql' as const, |
| 25 | + authentication: baseAuth, |
| 26 | + connectionConfig: { |
| 27 | + host: 'localhost', |
| 28 | + port: 5432, |
| 29 | + database: 'mydb', |
| 30 | + username: 'user', |
| 31 | + password: 'pass', |
| 32 | + }, |
| 33 | + tables: [minimalTable], |
| 34 | +}; |
| 35 | + |
| 36 | +describe('DatabaseProviderSchema', () => { |
| 37 | + it('should accept all valid providers', () => { |
| 38 | + const providers = ['postgresql', 'mysql', 'mariadb', 'mssql', 'oracle', 'mongodb', 'redis', 'cassandra', 'snowflake', 'bigquery', 'redshift', 'custom']; |
| 39 | + for (const p of providers) { |
| 40 | + expect(DatabaseProviderSchema.parse(p)).toBe(p); |
| 41 | + } |
| 42 | + }); |
| 43 | + |
| 44 | + it('should reject invalid provider', () => { |
| 45 | + expect(() => DatabaseProviderSchema.parse('sqlite')).toThrow(); |
| 46 | + }); |
| 47 | +}); |
| 48 | + |
| 49 | +describe('DatabasePoolConfigSchema', () => { |
| 50 | + it('should apply defaults', () => { |
| 51 | + const result = DatabasePoolConfigSchema.parse({}); |
| 52 | + expect(result.min).toBe(2); |
| 53 | + expect(result.max).toBe(10); |
| 54 | + expect(result.idleTimeoutMs).toBe(30000); |
| 55 | + expect(result.testOnBorrow).toBe(true); |
| 56 | + }); |
| 57 | + |
| 58 | + it('should accept custom values', () => { |
| 59 | + const result = DatabasePoolConfigSchema.parse({ min: 0, max: 50, idleTimeoutMs: 5000 }); |
| 60 | + expect(result.min).toBe(0); |
| 61 | + expect(result.max).toBe(50); |
| 62 | + }); |
| 63 | + |
| 64 | + it('should reject max below 1', () => { |
| 65 | + expect(() => DatabasePoolConfigSchema.parse({ max: 0 })).toThrow(); |
| 66 | + }); |
| 67 | + |
| 68 | + it('should reject idleTimeoutMs below 1000', () => { |
| 69 | + expect(() => DatabasePoolConfigSchema.parse({ idleTimeoutMs: 500 })).toThrow(); |
| 70 | + }); |
| 71 | +}); |
| 72 | + |
| 73 | +describe('SslConfigSchema', () => { |
| 74 | + it('should apply defaults', () => { |
| 75 | + const result = SslConfigSchema.parse({}); |
| 76 | + expect(result.enabled).toBe(false); |
| 77 | + expect(result.rejectUnauthorized).toBe(true); |
| 78 | + }); |
| 79 | + |
| 80 | + it('should accept full config', () => { |
| 81 | + const result = SslConfigSchema.parse({ |
| 82 | + enabled: true, |
| 83 | + rejectUnauthorized: false, |
| 84 | + ca: 'ca-cert', |
| 85 | + cert: 'client-cert', |
| 86 | + key: 'client-key', |
| 87 | + }); |
| 88 | + expect(result.enabled).toBe(true); |
| 89 | + expect(result.ca).toBe('ca-cert'); |
| 90 | + }); |
| 91 | +}); |
| 92 | + |
| 93 | +describe('CdcConfigSchema', () => { |
| 94 | + it('should accept valid CDC config', () => { |
| 95 | + const result = CdcConfigSchema.parse({ method: 'log_based' }); |
| 96 | + expect(result.enabled).toBe(false); |
| 97 | + expect(result.batchSize).toBe(1000); |
| 98 | + expect(result.pollIntervalMs).toBe(1000); |
| 99 | + }); |
| 100 | + |
| 101 | + it('should accept all CDC methods', () => { |
| 102 | + for (const m of ['log_based', 'trigger_based', 'query_based', 'custom']) { |
| 103 | + expect(() => CdcConfigSchema.parse({ method: m })).not.toThrow(); |
| 104 | + } |
| 105 | + }); |
| 106 | + |
| 107 | + it('should reject missing method', () => { |
| 108 | + expect(() => CdcConfigSchema.parse({ enabled: true })).toThrow(); |
| 109 | + }); |
| 110 | + |
| 111 | + it('should reject batchSize out of range', () => { |
| 112 | + expect(() => CdcConfigSchema.parse({ method: 'log_based', batchSize: 0 })).toThrow(); |
| 113 | + expect(() => CdcConfigSchema.parse({ method: 'log_based', batchSize: 10001 })).toThrow(); |
| 114 | + }); |
| 115 | + |
| 116 | + it('should accept optional fields', () => { |
| 117 | + const result = CdcConfigSchema.parse({ |
| 118 | + method: 'log_based', |
| 119 | + enabled: true, |
| 120 | + slotName: 'slot1', |
| 121 | + publicationName: 'pub1', |
| 122 | + startPosition: '0/1234', |
| 123 | + }); |
| 124 | + expect(result.slotName).toBe('slot1'); |
| 125 | + }); |
| 126 | +}); |
| 127 | + |
| 128 | +describe('DatabaseTableSchema', () => { |
| 129 | + it('should accept valid table', () => { |
| 130 | + const result = DatabaseTableSchema.parse(minimalTable); |
| 131 | + expect(result.enabled).toBe(true); |
| 132 | + }); |
| 133 | + |
| 134 | + it('should accept table with all optional fields', () => { |
| 135 | + const data = { |
| 136 | + ...minimalTable, |
| 137 | + schema: 'public', |
| 138 | + enabled: false, |
| 139 | + fieldMappings: [{ source: 'ext_id', target: 'id' }], |
| 140 | + whereClause: 'status = \'active\'', |
| 141 | + }; |
| 142 | + expect(() => DatabaseTableSchema.parse(data)).not.toThrow(); |
| 143 | + }); |
| 144 | + |
| 145 | + it('should reject non-snake_case name', () => { |
| 146 | + expect(() => DatabaseTableSchema.parse({ ...minimalTable, name: 'Customer' })).toThrow(); |
| 147 | + }); |
| 148 | + |
| 149 | + it('should reject missing required fields', () => { |
| 150 | + expect(() => DatabaseTableSchema.parse({ name: 'tbl' })).toThrow(); |
| 151 | + }); |
| 152 | +}); |
| 153 | + |
| 154 | +describe('DatabaseConnectorSchema', () => { |
| 155 | + it('should accept minimal valid connector', () => { |
| 156 | + expect(() => DatabaseConnectorSchema.parse(minimalConnector)).not.toThrow(); |
| 157 | + }); |
| 158 | + |
| 159 | + it('should apply defaults', () => { |
| 160 | + const result = DatabaseConnectorSchema.parse(minimalConnector); |
| 161 | + expect(result.queryTimeoutMs).toBe(30000); |
| 162 | + expect(result.enableQueryLogging).toBe(false); |
| 163 | + expect(result.enabled).toBe(true); |
| 164 | + }); |
| 165 | + |
| 166 | + it('should accept full connector', () => { |
| 167 | + const full = { |
| 168 | + ...minimalConnector, |
| 169 | + poolConfig: { min: 5, max: 25 }, |
| 170 | + sslConfig: { enabled: true }, |
| 171 | + cdcConfig: { method: 'log_based', enabled: true }, |
| 172 | + readReplicaConfig: { |
| 173 | + enabled: true, |
| 174 | + hosts: [{ host: 'replica1', port: 5432, weight: 0.5 }], |
| 175 | + }, |
| 176 | + queryTimeoutMs: 60000, |
| 177 | + enableQueryLogging: true, |
| 178 | + }; |
| 179 | + expect(() => DatabaseConnectorSchema.parse(full)).not.toThrow(); |
| 180 | + }); |
| 181 | + |
| 182 | + it('should reject wrong type literal', () => { |
| 183 | + expect(() => DatabaseConnectorSchema.parse({ ...minimalConnector, type: 'saas' })).toThrow(); |
| 184 | + }); |
| 185 | + |
| 186 | + it('should reject invalid port', () => { |
| 187 | + expect(() => DatabaseConnectorSchema.parse({ |
| 188 | + ...minimalConnector, |
| 189 | + connectionConfig: { ...minimalConnector.connectionConfig, port: 0 }, |
| 190 | + })).toThrow(); |
| 191 | + expect(() => DatabaseConnectorSchema.parse({ |
| 192 | + ...minimalConnector, |
| 193 | + connectionConfig: { ...minimalConnector.connectionConfig, port: 70000 }, |
| 194 | + })).toThrow(); |
| 195 | + }); |
| 196 | + |
| 197 | + it('should reject queryTimeoutMs out of range', () => { |
| 198 | + expect(() => DatabaseConnectorSchema.parse({ ...minimalConnector, queryTimeoutMs: 500 })).toThrow(); |
| 199 | + expect(() => DatabaseConnectorSchema.parse({ ...minimalConnector, queryTimeoutMs: 500000 })).toThrow(); |
| 200 | + }); |
| 201 | + |
| 202 | + it('should reject missing tables', () => { |
| 203 | + const { tables: _, ...noTables } = minimalConnector; |
| 204 | + expect(() => DatabaseConnectorSchema.parse(noTables)).toThrow(); |
| 205 | + }); |
| 206 | + |
| 207 | + it('should reject read replica with invalid weight', () => { |
| 208 | + expect(() => DatabaseConnectorSchema.parse({ |
| 209 | + ...minimalConnector, |
| 210 | + readReplicaConfig: { |
| 211 | + enabled: true, |
| 212 | + hosts: [{ host: 'r1', port: 5432, weight: 2 }], |
| 213 | + }, |
| 214 | + })).toThrow(); |
| 215 | + }); |
| 216 | +}); |
0 commit comments