Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SKIP CI] add test for cli #100

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions packages/cli/base.graphql

This file was deleted.

11 changes: 0 additions & 11 deletions packages/cli/docker-compose.yml

This file was deleted.

13 changes: 0 additions & 13 deletions packages/cli/schema.graphql

This file was deleted.

60 changes: 60 additions & 0 deletions packages/cli/src/controller/codegen-controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2020-2021 OnFinality Limited authors & contributors
// SPDX-License-Identifier: Apache-2.0

import fs from 'fs';
import ejs from 'ejs';
import {renderTemplate, generateSchema} from './codegen-controller';
jest.mock('fs', () => {
const fsMock = jest.createMockFromModule('fs') as any;
fsMock.promises = {
writeFile: jest.fn(),
access: jest.fn(),
};
return fsMock;
});

jest.mock('ejs', () => {
return jest.createMockFromModule('ejs') as any;
});

const mockedTemplate = {
props: {
baseFolderPath: 'some_dir_path',
className: 'randomClass',
fields: ['field1', 'field2'],
},
};

describe('Codegen can generate schema (mocked)', () => {
it('generate schema should pass', async () => {
const schemaString = 'type goodEntity @entity {id: ID!}';
(fs.readFileSync as jest.Mock).mockReturnValue(schemaString);
await expect(generateSchema()).resolves.not.toThrow();
});

it('generate schema should fail', async () => {
const schemaString = 'type badEntity @entity {id: Float!}';
(fs.readFileSync as jest.Mock).mockReturnValue(schemaString);
await expect(generateSchema()).rejects.toThrow();
});

it('throw error when render ejs template in a folder missing models directory', async () => {
(fs.promises.access as jest.Mock).mockImplementation(async () => Promise.reject(new Error()));
await expect(renderTemplate('renderTemplateTest', mockedTemplate)).rejects.toThrow(
/Write schema failed, not in project directory/
);
});

it('render ejs template with an incorrect format modelTemplate should throw', async () => {
(fs.promises.access as jest.Mock).mockImplementation(async () => Promise.resolve());
(ejs.renderFile as jest.Mock).mockImplementation(async () => Promise.reject(new Error('render template failed')));
await expect(renderTemplate('renderTemplateTest', mockedTemplate)).rejects.toThrow(/render template failed/);
});

it('throw errors when write file failed in render template', async () => {
(fs.promises.access as jest.Mock).mockImplementation(async () => Promise.resolve());
(ejs.renderFile as jest.Mock).mockImplementation(async () => Promise.resolve());
(fs.promises.writeFile as jest.Mock).mockImplementation(async () => Promise.reject(new Error()));
await expect(renderTemplate('renderTemplateTest', mockedTemplate)).rejects.toThrow();
});
});
42 changes: 42 additions & 0 deletions packages/cli/src/controller/codegen-controller.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2020-2021 OnFinality Limited authors & contributors
// SPDX-License-Identifier: Apache-2.0

import path from 'path';
import {getAllEntities, buildSchema} from '@subql/common';
import {processFields} from './codegen-controller';
import {transformTypes} from './types-mapping';

describe('Codegen can generate schema', () => {
const badschema = buildSchema(path.join(__dirname, '../../test/badschema.graphql'));
const badextractEntities = getAllEntities(badschema);
const goodschema = buildSchema(path.join(__dirname, '../../test/schema.graphql'));
const goodextractEntities = getAllEntities(goodschema);

it('can transform field into correct type', () => {
const testClassName = 'transformTest;';
expect(transformTypes(testClassName, 'ID')).toBe('string');
expect(transformTypes(testClassName, 'Int')).toBe('number');
expect(transformTypes(testClassName, 'BigInt')).toBe('bigint');
expect(transformTypes(testClassName, 'String')).toBe('string');
expect(transformTypes(testClassName, 'Date')).toBe('Date');
});

it('process field with correct types should pass', () => {
const testClassName = 'processFieldTest';
expect.assertions(1);
for (const entity of goodextractEntities) {
const fields = entity.getFields();
const fieldList = processFields(testClassName, fields);
expect(fieldList).toBeDefined();
}
});

it('process field with unknown type to throw', () => {
const testClassName = 'processFieldTest';
for (const entity of badextractEntities) {
const fields = entity.getFields();
expect(() => processFields(testClassName, fields)).toThrow();
//Float in badschema is not support, should throw error
}
});
});
53 changes: 29 additions & 24 deletions packages/cli/src/controller/codegen-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,35 @@ import ejs from 'ejs';
import {isNonNullType, getNullableType} from 'graphql';
import {GraphQLFieldMap, GraphQLOutputType} from 'graphql/type/definition';
import {transformTypes} from './types-mapping';

const templatePath: string = path.resolve(__dirname, '../template/model.ts.ejs');
const typesPath = `${process.cwd()}/src/types/models`;

// 4. Save the rendered schema
function makeSchema(className: string, data: string): void {
// 3. Render entity data in ejs template and write it
export async function renderTemplate(className: string, modelTemplate: ejs.Data): Promise<void> {
const typesPath = path.resolve(`./src/types/models`);
const filename = `${className}.ts`;
const file = `${typesPath}/${filename}`;
let data: string;

fs.writeFile(file, data, (err) => {
if (err) {
console.error(err);
return;
}
console.log(`>--- Schema ${className} generated !`);
});
}
try {
await fs.promises.access(typesPath);
} catch (err) {
throw new Error('Write schema failed, not in project directory');
}

// 3. Render entity data in ejs template
export function renderTemplate(className: string, modelTemplate: ejs.Data): void {
ejs.renderFile(templatePath, modelTemplate, function (err, str) {
if (err) {
console.log(`!!! When render entity ${className} to schema have following problems !!! `);
console.log(err);
} else {
makeSchema(className, str);
}
});
try {
data = await ejs.renderFile(templatePath, modelTemplate);
} catch (err) {
throw new Error(err.message);
}

try {
await fs.promises.writeFile(file, data);
} catch (err) {
throw new Error(err.message);
} finally {
console.log(`---> Schema ${className} generated !`);
}
}

// 2. Re-format the field of the entity
Expand All @@ -50,6 +52,10 @@ export function processFields(className: string, fields: GraphQLFieldMap<unknown
if (Object.prototype.hasOwnProperty.call(fields, k)) {
const type: GraphQLOutputType = isNonNullType(fields[k].type) ? getNullableType(fields[k].type) : fields[k].type;
const newType = transformTypes(className, type.toString());
if (!newType) {
const errMsg = `Undefined type ${type.toString()} in Schema ${className}`;
throw new Error(errMsg);
}
fieldList.push({
name: fields[k].name,
type: newType,
Expand All @@ -61,10 +67,9 @@ export function processFields(className: string, fields: GraphQLFieldMap<unknown
}

// 1. Loop all entities and render it
export function generateSchema(): void {
export async function generateSchema(): Promise<void> {
const schema = buildSchema('./schema.graphql');
const extractEntities = getAllEntities(schema);

for (const entity of extractEntities) {
const baseFolderPath = '.../../base';
const className = entity.name;
Expand All @@ -78,6 +83,6 @@ export function generateSchema(): void {
},
};
console.log(`<--- Start generate schema ${modelTemplate.props.className}`);
renderTemplate(className, modelTemplate);
await renderTemplate(className, modelTemplate);
}
}
29 changes: 17 additions & 12 deletions packages/cli/src/controller/init-controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
// SPDX-License-Identifier: Apache-2.0

import * as fs from 'fs';
import rimraf from 'rimraf';
import {createProject} from './init-controller';

import os from 'os';
import path from 'path';
const projectName = 'mockStarterProject';
import {createProject} from './init-controller';

// async
const fileExists = async (file) => {
Expand All @@ -16,27 +16,32 @@ const fileExists = async (file) => {
});
};

describe('Cli can create project', () => {
beforeEach(async () => {
await new Promise((resolve) => rimraf(`${projectName}`, resolve));
});

afterEach(async () => {
await new Promise((resolve) => rimraf(`${projectName}`, resolve));
});
async function makeTempDir() {
const sep = path.sep;
const tmpDir = os.tmpdir();
const tempPath = await fs.promises.mkdtemp(`${tmpDir}${sep}`);
return tempPath;
}

describe('Cli can create project', () => {
it('should resolves when starter project successful created', async () => {
const tempPath = await makeTempDir();
process.chdir(tempPath);
await createProject(projectName);
await expect(fileExists(`./${projectName}`)).resolves.toEqual(true);
});

it('throw error if same name directory exists', async () => {
const tempPath = makeTempDir();
process.chdir(await tempPath);
fs.mkdirSync(`./${projectName}`);
await expect(createProject(projectName)).rejects.toThrow();
});

it('throw error if .git exists in starter project', async () => {
const tempPath = makeTempDir();
process.chdir(await tempPath);
await createProject(projectName);
await expect(fileExists(`./${projectName}/.git`)).rejects.toThrow();
await expect(fileExists(`${tempPath}/${projectName}/.git`)).rejects.toThrow();
});
});
5 changes: 3 additions & 2 deletions packages/cli/src/controller/types-mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ typeMap.set('Int', 'number');
typeMap.set('BigInt', 'bigint');
typeMap.set('String', 'string');
typeMap.set('Date', 'Date');
typeMap.set('Float', 'number');
typeMap.set('BigDecimal', 'number');
// TODO
// typeMap.set('Float', 'number');
// typeMap.set('BigDecimal', 'number');

export function transformTypes(className: string, fieldType: string): string {
const trimType = fieldType.trim();
Expand Down
1 change: 0 additions & 1 deletion packages/cli/test.sh

This file was deleted.

4 changes: 4 additions & 0 deletions packages/cli/test/badschema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type badEntity @entity {

id: Float #give the unknown type
}
12 changes: 12 additions & 0 deletions packages/cli/test/schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
type goodEntity @entity {

id: ID! #id is a required field

field1: Int!

field2: String #filed2 is an optional field

field3: BigInt

field4: Date
}