-
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add rule mandatory-columns * Describe mandatory-columns
- Loading branch information
Showing
4 changed files
with
325 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,268 @@ | ||
import { Schema } from "extract-pg-schema"; | ||
import { describe, expect, it, vi } from "vitest"; | ||
|
||
import DeepPartial from "../tests/DeepPartial"; | ||
import { mandatoryColumns } from "./mandatoryColumns"; | ||
|
||
describe("mandatoryColumns", () => { | ||
describe("no tables", () => { | ||
it("should pass when no tables exist", () => { | ||
const mockReporter = vi.fn(); | ||
const schemaObject: DeepPartial<Schema> = { | ||
tables: [], | ||
views: [], | ||
}; | ||
|
||
mandatoryColumns.process({ | ||
options: [{}], | ||
schemaObject: schemaObject as Schema, | ||
report: mockReporter, | ||
}); | ||
|
||
expect(mockReporter).toBeCalledTimes(0); | ||
}); | ||
}); | ||
|
||
describe("single mandatory column", () => { | ||
it("should pass when mandatory column exists", () => { | ||
const mockReporter = vi.fn(); | ||
const schemaObject: DeepPartial<Schema> = { | ||
name: "schema", | ||
tables: [ | ||
{ | ||
name: "test", | ||
columns: [ | ||
{ | ||
name: "id", | ||
expandedType: "pg_catalog.int4", | ||
ordinalPosition: 1, | ||
}, | ||
], | ||
}, | ||
], | ||
}; | ||
|
||
mandatoryColumns.process({ | ||
options: [{ id: { expandedType: "pg_catalog.int4" } }], | ||
schemaObject: schemaObject as Schema, | ||
report: mockReporter, | ||
}); | ||
|
||
expect(mockReporter).toBeCalledTimes(0); | ||
}); | ||
|
||
it("should report when mandatory column does not exist", () => { | ||
const mockReporter = vi.fn(); | ||
const schemaObject: DeepPartial<Schema> = { | ||
name: "schema", | ||
tables: [ | ||
{ | ||
name: "test", | ||
columns: [], | ||
}, | ||
], | ||
}; | ||
|
||
mandatoryColumns.process({ | ||
options: [{ id: { expandedType: "pg_catalog.int4" } }], | ||
schemaObject: schemaObject as Schema, | ||
report: mockReporter, | ||
}); | ||
|
||
expect(mockReporter).toBeCalledTimes(1); | ||
expect(mockReporter).toBeCalledWith( | ||
expect.objectContaining({ | ||
rule: "mandatory-columns", | ||
identifier: `schema.test`, | ||
message: `Mandatory column "id" is missing`, | ||
}), | ||
); | ||
}); | ||
|
||
it("should report when mandatory column exists but type differs", () => { | ||
const mockReporter = vi.fn(); | ||
const schemaObject: DeepPartial<Schema> = { | ||
name: "schema", | ||
tables: [ | ||
{ | ||
name: "test", | ||
columns: [ | ||
{ | ||
name: "id", | ||
expandedType: "pg_catalog.int2", | ||
ordinalPosition: 1, | ||
}, | ||
], | ||
}, | ||
], | ||
}; | ||
|
||
mandatoryColumns.process({ | ||
options: [{ id: { expandedType: "pg_catalog.int4" } }], | ||
schemaObject: schemaObject as Schema, | ||
report: mockReporter, | ||
}); | ||
|
||
expect(mockReporter).toBeCalledTimes(1); | ||
expect(mockReporter).toBeCalledWith( | ||
expect.objectContaining({ | ||
rule: "mandatory-columns", | ||
identifier: `schema.test.id`, | ||
message: `Column "id" has properties {"expandedType":"pg_catalog.int2"} but expected {"expandedType":"pg_catalog.int4"}`, | ||
}), | ||
); | ||
}); | ||
|
||
it("should report when mandatory column exists but ordinalPosition differs", () => { | ||
const mockReporter = vi.fn(); | ||
const schemaObject: DeepPartial<Schema> = { | ||
name: "schema", | ||
tables: [ | ||
{ | ||
name: "test", | ||
columns: [ | ||
{ | ||
name: "id", | ||
expandedType: "pg_catalog.int2", | ||
ordinalPosition: 1, | ||
}, | ||
], | ||
}, | ||
], | ||
}; | ||
|
||
mandatoryColumns.process({ | ||
options: [{ id: { ordinalPosition: 2 } }], | ||
schemaObject: schemaObject as Schema, | ||
report: mockReporter, | ||
}); | ||
|
||
expect(mockReporter).toBeCalledTimes(1); | ||
expect(mockReporter).toBeCalledWith( | ||
expect.objectContaining({ | ||
rule: "mandatory-columns", | ||
identifier: `schema.test.id`, | ||
message: `Column "id" has properties {"ordinalPosition":1} but expected {"ordinalPosition":2}`, | ||
}), | ||
); | ||
}); | ||
}); | ||
|
||
describe("multiple mandatory columns", () => { | ||
it("should pass when multiple mandatory columns exist", () => { | ||
const mockReporter = vi.fn(); | ||
const schemaObject: DeepPartial<Schema> = { | ||
name: "schema", | ||
tables: [ | ||
{ | ||
name: "test", | ||
columns: [ | ||
{ | ||
name: "id", | ||
expandedType: "pg_catalog.int4", | ||
ordinalPosition: 1, | ||
}, | ||
{ | ||
name: "created_at", | ||
expandedType: "pg_catalog.timestamptz", | ||
ordinalPosition: 2, | ||
}, | ||
], | ||
}, | ||
], | ||
}; | ||
|
||
mandatoryColumns.process({ | ||
options: [ | ||
{ | ||
id: { expandedType: "pg_catalog.int4" }, | ||
created_at: { expandedType: "pg_catalog.timestamptz" }, | ||
}, | ||
], | ||
schemaObject: schemaObject as Schema, | ||
report: mockReporter, | ||
}); | ||
|
||
expect(mockReporter).toBeCalledTimes(0); | ||
}); | ||
|
||
it("should report when one of the multiple mandatory columns does not exist", () => { | ||
const mockReporter = vi.fn(); | ||
const schemaObject: DeepPartial<Schema> = { | ||
name: "schema", | ||
tables: [ | ||
{ | ||
name: "test", | ||
columns: [ | ||
{ | ||
name: "id", | ||
expandedType: "pg_catalog.int4", | ||
ordinalPosition: 1, | ||
}, | ||
], | ||
}, | ||
], | ||
}; | ||
|
||
mandatoryColumns.process({ | ||
options: [ | ||
{ | ||
id: { expandedType: "pg_catalog.int4" }, | ||
created_at: { expandedType: "pg_catalog.timestamptz" }, | ||
}, | ||
], | ||
schemaObject: schemaObject as Schema, | ||
report: mockReporter, | ||
}); | ||
|
||
expect(mockReporter).toBeCalledTimes(1); | ||
expect(mockReporter).toBeCalledWith( | ||
expect.objectContaining({ | ||
rule: "mandatory-columns", | ||
identifier: `schema.test`, | ||
message: `Mandatory column "created_at" is missing`, | ||
}), | ||
); | ||
}); | ||
|
||
it("should report when multiple mandatory columns do not exist", () => { | ||
const mockReporter = vi.fn(); | ||
const schemaObject: DeepPartial<Schema> = { | ||
name: "schema", | ||
tables: [ | ||
{ | ||
name: "test", | ||
columns: [], | ||
}, | ||
], | ||
}; | ||
|
||
mandatoryColumns.process({ | ||
options: [ | ||
{ | ||
id: { expandedType: "pg_catalog.int4" }, | ||
created_at: { expandedType: "pg_catalog.timestamptz" }, | ||
}, | ||
], | ||
schemaObject: schemaObject as Schema, | ||
report: mockReporter, | ||
}); | ||
|
||
expect(mockReporter).toBeCalledTimes(2); | ||
expect(mockReporter).toBeCalledWith( | ||
expect.objectContaining({ | ||
rule: "mandatory-columns", | ||
identifier: `schema.test`, | ||
message: `Mandatory column "id" is missing`, | ||
}), | ||
); | ||
expect(mockReporter).toBeCalledWith( | ||
expect.objectContaining({ | ||
rule: "mandatory-columns", | ||
identifier: `schema.test`, | ||
message: `Mandatory column "created_at" is missing`, | ||
}), | ||
); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { TableColumn, TableDetails } from "extract-pg-schema"; | ||
import * as R from "ramda"; | ||
|
||
import Rule from "../Rule"; | ||
|
||
export const mandatoryColumns: Rule = { | ||
name: "mandatory-columns", | ||
docs: { | ||
description: | ||
"Require tables to have certain columns with certain properties", | ||
}, | ||
process({ options: [option], schemaObject, report }) { | ||
const expectedColumns: Record<string, Partial<TableColumn>> = option ?? {}; | ||
const validator = ({ name: tableName, columns }: TableDetails) => { | ||
const columnsByName = R.indexBy(R.prop("name"), columns); | ||
Object.entries(expectedColumns).forEach(([name, expectedProps]) => { | ||
const column = columnsByName[name]; | ||
if (!column) { | ||
report({ | ||
rule: this.name, | ||
identifier: `${schemaObject.name}.${tableName}`, | ||
message: `Mandatory column "${name}" is missing`, | ||
}); | ||
} else { | ||
const partialColumnProps = R.pick( | ||
Object.keys(expectedProps) as (keyof TableColumn)[], | ||
column, | ||
); | ||
if (!R.equals(partialColumnProps, expectedProps)) { | ||
report({ | ||
rule: this.name, | ||
identifier: `${schemaObject.name}.${tableName}.${column.name}`, | ||
message: `Column "${column.name}" has properties ${JSON.stringify(partialColumnProps)} but expected ${JSON.stringify(expectedProps)}`, | ||
}); | ||
} | ||
} | ||
}); | ||
}; | ||
schemaObject.tables.forEach(validator); | ||
}, | ||
}; |