Skip to content
This repository was archived by the owner on Jun 26, 2021. It is now read-only.

Commit

Permalink
Update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
typeofweb committed Apr 10, 2020
1 parent 99c3cc8 commit 723572d
Show file tree
Hide file tree
Showing 11 changed files with 193 additions and 71 deletions.
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
version: '2'

services:
devfaq_db:
image: postgres:11-alpine
typeofweb:
image: postgres:12-alpine
ports:
- '5432:5432'
environment:
Expand Down
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"scripts": {
"build": "tsc",
"test": "jest",
"lint": "eslint src"
"lint": "eslint src --ext .js,.jsx,.ts,.tsx"
},
"keywords": [],
"author": "Michał Miszczyszyn <[email protected]> (https://typeofweb.com/)",
Expand All @@ -32,20 +32,22 @@
"jest": "25.3.0",
"jest-extended": "0.11.5",
"lint-staged": "10.1.3",
"prettier": "2.0.4",
"ts-jest": "25.3.1",
"typescript": "3.8.3"
},
"dependencies": {
"pg-promise": "10.5.0"
"@types/lodash": "4.14.149",
"lodash": "4.17.15",
"pg-promise": "10.5.0",
"prettier": "2.0.4"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": "yarn lint --",
"*.{js,jsx,ts,tsx}": "yarn lint",
"*.{js,jsx,ts,tsx,md,html,css,json,yml}": "prettier --write"
}
}
24 changes: 0 additions & 24 deletions src/__tests__/db.ts

This file was deleted.

115 changes: 106 additions & 9 deletions src/__tests__/test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,113 @@
import 'jest-extended';
import fc from 'fast-check';
import { db, sql, pgp } from './db';
import { run } from '../generator';
import { db, sql, pgp } from '../generator/db';
import { getTablesSchemas, schemaToObj, tableToTSCode } from '../generator';
import Path from 'path';

const contains = (pattern: string, text: string) => text.indexOf(pattern) !== -1;
const contains = (pattern: string, text: string) => text.includes(pattern);

describe('test', () => {
beforeAll(() => db.none(sql('drop_user.sql')));
beforeEach(() => db.none(sql('create_user.sql')));
describe('unit tests', () => {
describe('schemaToObj', () => {
it('should generate schema object', () => {
const result = schemaToObj({
tableName: 'user',
schema: [
{
column_name: 'id',
udt_name: 'int4',
is_nullable: 'NO',
},
],
});

expect(result).toEqual({
name: 'user',
columns: {
id: { type: 'int4', notNull: true },
},
});
});

it('should handle nullable columns', () => {
const result = schemaToObj({
tableName: 'user',
schema: [
{
column_name: 'id',
udt_name: 'int4',
is_nullable: 'NO',
},
{
column_name: 'name',
udt_name: 'text',
is_nullable: 'YES',
},
],
});

expect(result).toEqual({
name: 'user',
columns: {
id: { type: 'int4', notNull: true },
name: { type: 'text', notNull: false },
},
});
});
});

describe('tableToTSCode', () => {
it('should generate valid TS code', () => {
expect(
tableToTSCode({
name: 'user',
columns: {
id: { type: 'int4', notNull: true },
name: { type: 'text', notNull: false },
},
}),
).toEqual(
`
const User = {
name: 'user',
columns: { id: { type: 'int4', notNull: true }, name: { type: 'text', notNull: false } },
} as const;
`.trimLeft(),
);
});

it('should allow passing formatting options to meet your style', () => {
expect(
tableToTSCode(
{
name: 'user',
columns: {
id: { type: 'int4', notNull: true },
name: { type: 'text', notNull: false },
},
},
{ printWidth: 80, useTabs: true },
),
).toEqual(
`
const User = {
name: 'user',
columns: {
id: { type: 'int4', notNull: true },
name: { type: 'text', notNull: false },
},
} as const;
`.trimLeft(),
);
});
});
});

describe('integration tests', () => {
beforeAll(() => db.none(sql(Path.join(__dirname, 'drop_user.sql'))));
beforeEach(() => db.none(sql(Path.join(__dirname, 'create_user.sql'))));

afterEach(async () => {
await db.none(sql('drop_user.sql'));
await db.none(sql(Path.join(__dirname, 'drop_user.sql')));
});

afterAll(() => pgp.end());
Expand All @@ -26,9 +123,9 @@ describe('test', () => {
});

it('correctly reads schema for table user', async () => {
const result = await run();
const result = await getTablesSchemas();

const userSchema = result.find(i => i.tableName === 'user');
const userSchema = result.find((i) => i.tableName === 'user');
expect(userSchema).toBeDefined();

expect(userSchema!.schema).toIncludeSameMembers([
Expand Down
4 changes: 1 addition & 3 deletions src/generator/db.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Path from 'path';
import PgPromise, { QueryFile } from 'pg-promise';

export const pgp = PgPromise();
Expand All @@ -11,8 +10,7 @@ export const db = pgp({

const sqlCache = new Map<string, QueryFile>();

export function sql(file: string) {
const fullPath = Path.join(__dirname, file);
export function sql(fullPath: string) {
if (sqlCache.has(fullPath)) {
return sqlCache.get(fullPath) as QueryFile;
}
Expand Down
59 changes: 48 additions & 11 deletions src/generator/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,68 @@
import { db, sql } from './db';
import { ColumnSchema, TableName } from './types';
import { ColumnSchema, TableName, TableSchema } from './types';
import Path from 'path';
import { Table } from '..';
import Prettier from 'prettier';
import { defaults } from 'lodash';

// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class NotSupportedError extends Error {
message = 'NotSupportedError';
}

function getTableNames(): Promise<TableName[]> {
return db.manyOrNone(sql('select_table_name.sql'));
return db.manyOrNone(sql(Path.join(__dirname, 'select_table_name.sql')));
}

function getTableSchema(tableName: string): Promise<ColumnSchema[]> {
return db.manyOrNone(sql('select_table_schema.sql'), [tableName]);
return db.manyOrNone(sql(Path.join(__dirname, 'select_table_schema.sql')), [tableName]);
}

async function getTablesSchemas(): Promise<
Array<{
tableName: string;
schema: ColumnSchema[];
}>
> {
export async function getTablesSchemas(): Promise<Array<TableSchema>> {
const tableNames = await getTableNames();

const result = await Promise.all(
tableNames.map(async i => {
tableNames.map(async (i) => {
return { tableName: i.table_name, schema: await getTableSchema(i.table_name) };
}),
);

return result;
}

export function schemaToObj(schema: TableSchema): Table {
return {
name: schema.tableName,
columns: Object.fromEntries(
schema.schema.map((s) => {
return [s.column_name, { type: s.udt_name, notNull: s.is_nullable === 'NO' }];
}),
),
};
}

export function tableToTSCode(table: Table, opts?: Prettier.Options): string {
const typeName = table.name.slice(0, 1).toLocaleUpperCase() + table.name.slice(1);
const code = `const ${typeName} = ${JSON.stringify(table)} as const;`;

const defaultOptions: Prettier.Options = {
semi: true,
singleQuote: true,
trailingComma: 'all',
printWidth: 100,
tabWidth: 2,
useTabs: false,
} as const;
const options = defaults({}, opts, defaultOptions);
return Prettier.format(code, {
...options,
parser: 'typescript',
});
}

export async function run() {
return getTablesSchemas();
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const schemas = await getTablesSchemas();
}
5 changes: 5 additions & 0 deletions src/generator/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,8 @@ export type ColumnSchema = {
udt_name: ColumnType;
is_nullable: 'YES' | 'NO';
};

export type TableSchema = {
tableName: TableName['table_name'];
schema: ColumnSchema[];
};
4 changes: 2 additions & 2 deletions src/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { db, Op } from './index';
const User = {
name: 'user',
columns: {
age: { type: 'TEXT', notNull: false },
id: { type: 'TINYINT', notNull: true },
age: { type: 'text', notNull: false },
id: { type: 'int4', notNull: true },
},
} as const;

Expand Down
28 changes: 14 additions & 14 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ColumnType } from './generator/types';
const $eq = Symbol('$eq');
const $neq = Symbol('$neq');
const $in = Symbol('$in');
Expand All @@ -15,28 +16,27 @@ type OperandTypeForOperator<O extends Operators, T> = {
[$in]: T[];
}[O];

type Table = {
export type Table = {
name: string;
columns: Record<string, ColumnMetaData<Table>>;
};

/**
* @description Types of columns which are mappable to JS
*/
type ColumnMetaDataType = 'TEXT' | 'DATE' | 'TINYINT';

type Pretty<T> = { [K in keyof T]: T[K] };

/**
* @description Convert SQL column string literal type to JavaScript type
*/
type GetJSTypeFromSqlType<T extends ColumnMetaDataType, Nullable extends boolean> =
| {
DATE: Date;
TEXT: string;
TINYINT: number;
}[T]
| (Nullable extends false ? null : never);
type SupportedTypes = {
date: Date;
text: string;
int4: number;
};
type GetJSTypeFromSqlType<
T extends ColumnType,
Nullable extends boolean
> = T extends keyof SupportedTypes
? SupportedTypes[T] | (Nullable extends false ? null : never)
: never;

type GetColumnJSType<
SelectedTable extends Table,
Expand All @@ -51,7 +51,7 @@ type GetColumnJSType<
/**
* @description information about column such as if it's nullable, foreign key, autoincrement etc.
*/
type ColumnMetaData<_M extends Table, Type extends ColumnMetaDataType = ColumnMetaDataType> = {
type ColumnMetaData<_M extends Table, Type extends ColumnType = ColumnType> = {
type: Type;
notNull: boolean;
// … @todo
Expand Down
4 changes: 3 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
"emitDecoratorMetadata": true,
"outDir": "./lib",
"lib": ["esnext"],
"skipLibCheck": true
"skipLibCheck": true,
"noUnusedLocals": false,
"noUnusedParameters": false
},
"include": ["src"]
}
Loading

0 comments on commit 723572d

Please sign in to comment.