| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- import assert from 'node:assert/strict';
- import { test } from 'node:test';
- import { schemaSnapshotSchema, validateSchemaSnapshot } from './index.js';
- const okColumn = {
- sqlType: 'text',
- nullable: false,
- primaryKey: true,
- unique: false,
- };
- test('accepts a legitimate snapshot', () => {
- const ok = validateSchemaSnapshot({
- version: 1,
- tables: {
- notes: {
- columns: {
- id: okColumn,
- body: { sqlType: 'text', nullable: false, primaryKey: false, unique: false },
- createdAt: {
- sqlType: 'timestamptz',
- nullable: false,
- primaryKey: false,
- unique: false,
- default: 'now()',
- },
- },
- indexes: [],
- },
- posts: {
- columns: {
- id: okColumn,
- userId: {
- sqlType: 'text',
- nullable: false,
- primaryKey: false,
- unique: false,
- references: { table: 'notes', column: 'id', onDelete: 'cascade' },
- },
- },
- indexes: [{ columns: ['userId'], unique: false }],
- },
- },
- });
- assert.equal(ok.version, 1);
- assert.equal(Object.keys(ok.tables).length, 2);
- });
- test('rejects table name with quote-injection', () => {
- assert.throws(() =>
- validateSchemaSnapshot({
- version: 1,
- tables: {
- 'x" (id text); DROP SCHEMA "proj_other" CASCADE; CREATE TABLE "y': {
- columns: { id: okColumn },
- indexes: [],
- },
- },
- }),
- );
- });
- test('rejects table name with reserved _briven_ prefix', () => {
- assert.throws(() =>
- validateSchemaSnapshot({
- version: 1,
- tables: {
- _briven_meta: { columns: { id: okColumn }, indexes: [] },
- },
- }),
- );
- });
- test('rejects column name with quote-injection', () => {
- assert.throws(() =>
- validateSchemaSnapshot({
- version: 1,
- tables: {
- notes: {
- columns: { 'id" text); DROP TABLE "users': okColumn },
- indexes: [],
- },
- },
- }),
- );
- });
- test('rejects sqlType outside allowlist', () => {
- assert.throws(() =>
- validateSchemaSnapshot({
- version: 1,
- tables: {
- notes: {
- columns: {
- id: {
- sqlType: 'text PRIMARY KEY); DROP SCHEMA other CASCADE; --',
- nullable: false,
- primaryKey: true,
- unique: false,
- },
- },
- indexes: [],
- },
- },
- }),
- );
- });
- test('accepts sqlType with parametric forms (varchar, vector)', () => {
- const ok = validateSchemaSnapshot({
- version: 1,
- tables: {
- docs: {
- columns: {
- id: okColumn,
- title: { sqlType: 'varchar(255)', nullable: false, primaryKey: false, unique: false },
- embedding: { sqlType: 'vector(1536)', nullable: true, primaryKey: false, unique: false },
- },
- indexes: [],
- },
- },
- });
- assert.equal(ok.tables.docs?.columns.title?.sqlType, 'varchar(255)');
- });
- test('rejects default that breaks out of literal context', () => {
- assert.throws(() =>
- validateSchemaSnapshot({
- version: 1,
- tables: {
- notes: {
- columns: {
- id: okColumn,
- body: {
- sqlType: 'text',
- nullable: false,
- primaryKey: false,
- unique: false,
- default: "''); DROP TABLE users; --",
- },
- },
- indexes: [],
- },
- },
- }),
- );
- });
- test('accepts safe default forms', () => {
- for (const def of [
- 'now()',
- 'gen_random_uuid()',
- 'true',
- 'false',
- 'null',
- '0',
- '-1.5',
- "'hello'",
- 'current_timestamp',
- ]) {
- validateSchemaSnapshot({
- version: 1,
- tables: {
- t: {
- columns: {
- id: okColumn,
- v: {
- sqlType: 'text',
- nullable: true,
- primaryKey: false,
- unique: false,
- default: def,
- },
- },
- indexes: [],
- },
- },
- });
- }
- });
- test('rejects FK references with invalid identifier', () => {
- assert.throws(() =>
- validateSchemaSnapshot({
- version: 1,
- tables: {
- posts: {
- columns: {
- id: okColumn,
- userId: {
- sqlType: 'text',
- nullable: false,
- primaryKey: false,
- unique: false,
- references: { table: 'users"; DROP TABLE x; --', column: 'id' },
- },
- },
- indexes: [],
- },
- },
- }),
- );
- });
- test('rejects onDelete outside enum', () => {
- assert.throws(() =>
- validateSchemaSnapshot({
- version: 1,
- tables: {
- posts: {
- columns: {
- id: okColumn,
- userId: {
- sqlType: 'text',
- nullable: false,
- primaryKey: false,
- unique: false,
- references: {
- table: 'users',
- column: 'id',
- onDelete: 'CASCADE; DROP TABLE x; --' as 'cascade',
- },
- },
- },
- indexes: [],
- },
- },
- }),
- );
- });
- test('rejects index columns with invalid identifier', () => {
- assert.throws(() =>
- validateSchemaSnapshot({
- version: 1,
- tables: {
- notes: {
- columns: { id: okColumn },
- indexes: [{ columns: ['id"); DROP TABLE x; --'], unique: false }],
- },
- },
- }),
- );
- });
- test('rejects version other than 1', () => {
- assert.throws(() =>
- validateSchemaSnapshot({
- version: 2 as 1,
- tables: {},
- }),
- );
- });
- test('schemaSnapshotSchema is exported as a Zod schema', () => {
- assert.equal(typeof schemaSnapshotSchema.parse, 'function');
- });
|