From 040d9288e30baa6013e3ccf213204b856abcf9ad Mon Sep 17 00:00:00 2001 From: smartass Date: Thu, 11 Jun 2026 23:03:43 +0500 Subject: [PATCH] feat: add connection and ssh zod schemas --- src/config/types.ts | 38 ++++++++++++++++++++++++++ test/unit/config/types.test.ts | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 src/config/types.ts create mode 100644 test/unit/config/types.test.ts diff --git a/src/config/types.ts b/src/config/types.ts new file mode 100644 index 0000000..ae0552c --- /dev/null +++ b/src/config/types.ts @@ -0,0 +1,38 @@ +import * as z from 'zod' + +export const sshConfigSchema = z.object({ + host: z.string().min(1), + port: z.number().int().positive().default(22), + user: z.string().min(1), + password: z.string().optional(), + privateKey: z.string().optional(), + privateKeyPath: z.string().optional(), + passphrase: z.string().optional(), + agent: z.boolean().optional() +}) + +export const connectionConfigSchema = z.object({ + name: z.string().regex(/^[a-zA-Z0-9_-]+$/, 'name must match [a-zA-Z0-9_-]+'), + type: z.enum(['postgres', 'mysql']), + host: z.string().min(1), + port: z.number().int().positive().optional(), + user: z.string().min(1), + password: z.string().optional(), + database: z.string().optional(), + readonly: z.boolean().default(false), + ssh: sshConfigSchema.optional() +}) + +export type SshConfig = z.infer +export type ConnectionConfig = z.infer + +export type ConnectionSource = 'env' | 'config' | 'store' + +export type ResolvedConnection = { + config: ConnectionConfig + source: ConnectionSource + hash: string +} + +export const defaultPort = (type: ConnectionConfig['type']): number => + type === 'postgres' ? 5432 : 3306 diff --git a/test/unit/config/types.test.ts b/test/unit/config/types.test.ts new file mode 100644 index 0000000..fd9f5b9 --- /dev/null +++ b/test/unit/config/types.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest' +import { connectionConfigSchema, defaultPort } from '../../../src/config/types.js' + +describe('connectionConfigSchema', () => { + const minimal = { + name: 'lab-pg', + type: 'postgres', + host: 'localhost', + user: 'postgres' + } + + it('accepts minimal config and applies defaults', () => { + const parsed = connectionConfigSchema.parse(minimal) + expect(parsed.readonly).toBe(false) + expect(parsed.port).toBeUndefined() + expect(parsed.ssh).toBeUndefined() + }) + + it('accepts full config with ssh and defaults ssh port to 22', () => { + const parsed = connectionConfigSchema.parse({ + ...minimal, + port: 5433, + password: 'secret', + database: 'app', + readonly: true, + ssh: { host: 'bastion', user: 'root', privateKeyPath: '~/.ssh/id_ed25519' } + }) + expect(parsed.ssh?.port).toBe(22) + expect(parsed.readonly).toBe(true) + }) + + it('rejects bad name characters', () => { + expect(() => connectionConfigSchema.parse({ ...minimal, name: 'bad name!' })).toThrow() + }) + + it('rejects unknown engine type', () => { + expect(() => connectionConfigSchema.parse({ ...minimal, type: 'oracle' })).toThrow() + }) + + it('rejects empty host', () => { + expect(() => connectionConfigSchema.parse({ ...minimal, host: '' })).toThrow() + }) +}) + +describe('defaultPort', () => { + it('returns 5432 for postgres and 3306 for mysql', () => { + expect(defaultPort('postgres')).toBe(5432) + expect(defaultPort('mysql')).toBe(3306) + }) +})