|
| 1 | +import { describe, it, expect } from 'vitest'; |
| 2 | +import type { IDataEngine, DriverInterface } from './data-engine'; |
| 3 | + |
| 4 | +describe('Data Engine Contract', () => { |
| 5 | + describe('IDataEngine interface', () => { |
| 6 | + it('should allow a minimal implementation with required methods', () => { |
| 7 | + const engine: IDataEngine = { |
| 8 | + find: async (_objectName, _query?) => [], |
| 9 | + findOne: async (_objectName, _query?) => null, |
| 10 | + insert: async (_objectName, data, _options?) => data, |
| 11 | + update: async (_objectName, data, _options?) => data, |
| 12 | + delete: async (_objectName, _options?) => { return { deleted: 1 }; }, |
| 13 | + count: async (_objectName, _query?) => 0, |
| 14 | + aggregate: async (_objectName, _query) => [], |
| 15 | + }; |
| 16 | + |
| 17 | + expect(typeof engine.find).toBe('function'); |
| 18 | + expect(typeof engine.findOne).toBe('function'); |
| 19 | + expect(typeof engine.insert).toBe('function'); |
| 20 | + expect(typeof engine.update).toBe('function'); |
| 21 | + expect(typeof engine.delete).toBe('function'); |
| 22 | + expect(typeof engine.count).toBe('function'); |
| 23 | + expect(typeof engine.aggregate).toBe('function'); |
| 24 | + }); |
| 25 | + |
| 26 | + it('should perform CRUD operations', async () => { |
| 27 | + const store: any[] = []; |
| 28 | + |
| 29 | + const engine: IDataEngine = { |
| 30 | + find: async () => [...store], |
| 31 | + findOne: async () => store[0] || null, |
| 32 | + insert: async (_obj, data) => { |
| 33 | + store.push(data); |
| 34 | + return data; |
| 35 | + }, |
| 36 | + update: async (_obj, data) => data, |
| 37 | + delete: async () => ({ deleted: 1 }), |
| 38 | + count: async () => store.length, |
| 39 | + aggregate: async () => [], |
| 40 | + }; |
| 41 | + |
| 42 | + await engine.insert('users', { id: 1, name: 'Alice' }); |
| 43 | + await engine.insert('users', { id: 2, name: 'Bob' }); |
| 44 | + |
| 45 | + const all = await engine.find('users'); |
| 46 | + expect(all).toHaveLength(2); |
| 47 | + |
| 48 | + const first = await engine.findOne('users'); |
| 49 | + expect(first).toEqual({ id: 1, name: 'Alice' }); |
| 50 | + |
| 51 | + const count = await engine.count('users'); |
| 52 | + expect(count).toBe(2); |
| 53 | + }); |
| 54 | + |
| 55 | + it('should support optional vectorFind', async () => { |
| 56 | + const engine: IDataEngine = { |
| 57 | + find: async () => [], |
| 58 | + findOne: async () => null, |
| 59 | + insert: async (_obj, data) => data, |
| 60 | + update: async (_obj, data) => data, |
| 61 | + delete: async () => ({}), |
| 62 | + count: async () => 0, |
| 63 | + aggregate: async () => [], |
| 64 | + vectorFind: async (_objectName, _vector, options?) => { |
| 65 | + return [{ id: 1, score: 0.95 }].slice(0, options?.limit ?? 10); |
| 66 | + }, |
| 67 | + }; |
| 68 | + |
| 69 | + expect(engine.vectorFind).toBeDefined(); |
| 70 | + const results = await engine.vectorFind!('documents', [0.1, 0.2, 0.3], { |
| 71 | + limit: 5, |
| 72 | + threshold: 0.8, |
| 73 | + }); |
| 74 | + expect(results).toHaveLength(1); |
| 75 | + expect(results[0].score).toBe(0.95); |
| 76 | + }); |
| 77 | + |
| 78 | + it('should support optional batch operations', async () => { |
| 79 | + const engine: IDataEngine = { |
| 80 | + find: async () => [], |
| 81 | + findOne: async () => null, |
| 82 | + insert: async (_obj, data) => data, |
| 83 | + update: async (_obj, data) => data, |
| 84 | + delete: async () => ({}), |
| 85 | + count: async () => 0, |
| 86 | + aggregate: async () => [], |
| 87 | + batch: async (requests, options?) => { |
| 88 | + return requests.map(() => ({ success: true })); |
| 89 | + }, |
| 90 | + }; |
| 91 | + |
| 92 | + expect(engine.batch).toBeDefined(); |
| 93 | + const results = await engine.batch!( |
| 94 | + [ |
| 95 | + { object: 'users', operation: 'insert', data: { name: 'Alice' } } as any, |
| 96 | + { object: 'users', operation: 'insert', data: { name: 'Bob' } } as any, |
| 97 | + ], |
| 98 | + { transaction: true } |
| 99 | + ); |
| 100 | + expect(results).toHaveLength(2); |
| 101 | + }); |
| 102 | + |
| 103 | + it('should support optional execute (escape hatch)', async () => { |
| 104 | + const engine: IDataEngine = { |
| 105 | + find: async () => [], |
| 106 | + findOne: async () => null, |
| 107 | + insert: async (_obj, data) => data, |
| 108 | + update: async (_obj, data) => data, |
| 109 | + delete: async () => ({}), |
| 110 | + count: async () => 0, |
| 111 | + aggregate: async () => [], |
| 112 | + execute: async (command, options?) => { |
| 113 | + return { raw: true, command }; |
| 114 | + }, |
| 115 | + }; |
| 116 | + |
| 117 | + expect(engine.execute).toBeDefined(); |
| 118 | + const result = await engine.execute!('SELECT * FROM users', { timeout: 5000 }); |
| 119 | + expect(result.raw).toBe(true); |
| 120 | + }); |
| 121 | + }); |
| 122 | + |
| 123 | + describe('DriverInterface', () => { |
| 124 | + it('should allow a minimal implementation with required methods', () => { |
| 125 | + const driver: DriverInterface = { |
| 126 | + name: 'postgres', |
| 127 | + version: '1.0.0', |
| 128 | + connect: async () => {}, |
| 129 | + disconnect: async () => {}, |
| 130 | + find: async (_object, _query, _options?) => [], |
| 131 | + findOne: async (_object, _query, _options?) => null, |
| 132 | + create: async (_object, data, _options?) => data, |
| 133 | + update: async (_object, _id, data, _options?) => data, |
| 134 | + delete: async (_object, _id, _options?) => ({ deleted: true }), |
| 135 | + }; |
| 136 | + |
| 137 | + expect(driver.name).toBe('postgres'); |
| 138 | + expect(driver.version).toBe('1.0.0'); |
| 139 | + expect(typeof driver.connect).toBe('function'); |
| 140 | + expect(typeof driver.disconnect).toBe('function'); |
| 141 | + }); |
| 142 | + |
| 143 | + it('should connect and disconnect', async () => { |
| 144 | + let connected = false; |
| 145 | + |
| 146 | + const driver: DriverInterface = { |
| 147 | + name: 'mongo', |
| 148 | + version: '2.0.0', |
| 149 | + connect: async () => { connected = true; }, |
| 150 | + disconnect: async () => { connected = false; }, |
| 151 | + find: async () => [], |
| 152 | + findOne: async () => null, |
| 153 | + create: async (_obj, data) => data, |
| 154 | + update: async (_obj, _id, data) => data, |
| 155 | + delete: async () => ({}), |
| 156 | + }; |
| 157 | + |
| 158 | + await driver.connect(); |
| 159 | + expect(connected).toBe(true); |
| 160 | + |
| 161 | + await driver.disconnect(); |
| 162 | + expect(connected).toBe(false); |
| 163 | + }); |
| 164 | + |
| 165 | + it('should support optional bulk operations', async () => { |
| 166 | + const driver: DriverInterface = { |
| 167 | + name: 'postgres', |
| 168 | + version: '1.0.0', |
| 169 | + connect: async () => {}, |
| 170 | + disconnect: async () => {}, |
| 171 | + find: async () => [], |
| 172 | + findOne: async () => null, |
| 173 | + create: async (_obj, data) => data, |
| 174 | + update: async (_obj, _id, data) => data, |
| 175 | + delete: async () => ({}), |
| 176 | + bulkCreate: async (_obj, data) => data, |
| 177 | + updateMany: async (_obj, _query, _data) => ({ modified: 5 }), |
| 178 | + deleteMany: async (_obj, _query) => ({ deleted: 3 }), |
| 179 | + }; |
| 180 | + |
| 181 | + expect(driver.bulkCreate).toBeDefined(); |
| 182 | + expect(driver.updateMany).toBeDefined(); |
| 183 | + expect(driver.deleteMany).toBeDefined(); |
| 184 | + |
| 185 | + const bulk = await driver.bulkCreate!('users', [{ name: 'A' }, { name: 'B' }]); |
| 186 | + expect(bulk).toHaveLength(2); |
| 187 | + }); |
| 188 | + |
| 189 | + it('should support optional count and execute', async () => { |
| 190 | + const driver: DriverInterface = { |
| 191 | + name: 'sqlite', |
| 192 | + version: '3.0.0', |
| 193 | + connect: async () => {}, |
| 194 | + disconnect: async () => {}, |
| 195 | + find: async () => [], |
| 196 | + findOne: async () => null, |
| 197 | + create: async (_obj, data) => data, |
| 198 | + update: async (_obj, _id, data) => data, |
| 199 | + delete: async () => ({}), |
| 200 | + count: async (_obj, _query) => 42, |
| 201 | + execute: async (command, _params?) => ({ rows: [], command }), |
| 202 | + }; |
| 203 | + |
| 204 | + expect(await driver.count!('users', {} as any)).toBe(42); |
| 205 | + const result = await driver.execute!('PRAGMA table_info(users)'); |
| 206 | + expect(result.command).toBe('PRAGMA table_info(users)'); |
| 207 | + }); |
| 208 | + }); |
| 209 | +}); |
0 commit comments