nx/e2e/node/src/node.test.ts
2023-05-03 11:02:07 -04:00

543 lines
16 KiB
TypeScript

import { stripIndents } from '@angular-devkit/core/src/utils/literals';
import { joinPathFragments } from '@nx/devkit';
import {
checkFilesDoNotExist,
checkFilesExist,
cleanupProject,
createFile,
detectPackageManager,
expectJestTestsToPass,
getPackageManagerCommand,
killPorts,
newProject,
packageInstall,
packageManagerLockFile,
promisifiedTreeKill,
readFile,
runCLI,
runCLIAsync,
runCommand,
runCommandUntil,
tmpProjPath,
uniq,
updateFile,
updateProjectConfig,
} from '@nx/e2e/utils';
import { exec, execSync } from 'child_process';
import * as http from 'http';
import { getLockFileName } from '@nx/js';
import { satisfies } from 'semver';
function getData(port, path = '/api'): Promise<any> {
return new Promise((resolve) => {
http.get(`http://localhost:${port}${path}`, (res) => {
expect(res.statusCode).toEqual(200);
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.once('end', () => {
try {
resolve(JSON.parse(data));
} catch (e) {
resolve(data);
}
});
});
});
}
describe('Node Applications', () => {
beforeEach(() => newProject());
afterEach(() => cleanupProject());
it('should be able to generate an empty application', async () => {
const nodeapp = uniq('nodeapp');
runCLI(`generate @nx/node:app ${nodeapp} --linter=eslint`);
const lintResults = runCLI(`lint ${nodeapp}`);
expect(lintResults).toContain('All files pass linting.');
updateFile(`apps/${nodeapp}/src/main.ts`, `console.log('Hello World!');`);
await runCLIAsync(`build ${nodeapp}`);
checkFilesExist(`dist/apps/${nodeapp}/main.js`);
const result = execSync(`node dist/apps/${nodeapp}/main.js`, {
cwd: tmpProjPath(),
}).toString();
expect(result).toContain('Hello World!');
}, 300000);
it('should be able to generate the correct outputFileName in options', async () => {
const nodeapp = uniq('nodeapp');
runCLI(`generate @nx/node:app ${nodeapp} --linter=eslint`);
updateProjectConfig(nodeapp, (config) => {
config.targets.build.options.outputFileName = 'index.js';
return config;
});
await runCLIAsync(`build ${nodeapp}`);
checkFilesExist(`dist/apps/${nodeapp}/index.js`);
}, 300000);
it('should be able to generate an empty application with additional entries', async () => {
const nodeapp = uniq('nodeapp');
runCLI(
`generate @nx/node:app ${nodeapp} --linter=eslint --bundler=webpack`
);
const lintResults = runCLI(`lint ${nodeapp}`);
expect(lintResults).toContain('All files pass linting.');
updateProjectConfig(nodeapp, (config) => {
config.targets.build.options.additionalEntryPoints = [
{
entryName: 'additional-main',
entryPath: `apps/${nodeapp}/src/additional-main.ts`,
},
];
return config;
});
updateFile(
`apps/${nodeapp}/src/additional-main.ts`,
`console.log('Hello Additional World!');`
);
updateFile(`apps/${nodeapp}/src/main.ts`, `console.log('Hello World!');`);
await runCLIAsync(`build ${nodeapp}`);
checkFilesExist(
`dist/apps/${nodeapp}/main.js`,
`dist/apps/${nodeapp}/additional-main.js`
);
const result = execSync(`node dist/apps/${nodeapp}/main.js`, {
cwd: tmpProjPath(),
}).toString();
expect(result).toContain('Hello World!');
const additionalResult = execSync(
`node dist/apps/${nodeapp}/additional-main.js`,
{
cwd: tmpProjPath(),
}
).toString();
expect(additionalResult).toContain('Hello Additional World!');
}, 60000);
it('should be able to generate an express application', async () => {
const nodeapp = uniq('nodeapp');
const originalEnvPort = process.env.PORT;
const port = 3456;
process.env.PORT = `${port}`;
runCLI(`generate @nx/express:app ${nodeapp} --linter=eslint`);
const lintResults = runCLI(`lint ${nodeapp}`);
expect(lintResults).toContain('All files pass linting.');
updateFile(
`apps/${nodeapp}/src/app/test.spec.ts`,
`
describe('test', () => {
it('should work', () => {
expect(true).toEqual(true);
})
})
`
);
const jestResult = runCLI(`test ${nodeapp}`);
expect(jestResult).toContain('Successfully ran target test');
// checking serve
updateFile(`apps/${nodeapp}/src/assets/file.txt`, `Test`);
const p = await runCommandUntil(`serve ${nodeapp}`, (output) =>
output.includes(`Listening at http://localhost:${port}`)
);
let result = await getData(port);
expect(result.message).toMatch(`Welcome to ${nodeapp}!`);
result = await getData(port, '/assets/file.txt');
expect(result).toMatch(`Test`);
try {
await promisifiedTreeKill(p.pid, 'SIGKILL');
await killPorts(port);
} finally {
process.env.port = originalEnvPort;
}
}, 120_000);
xit('should be able to generate a nest application', async () => {
const nestapp = uniq('nestapp');
const port = 3335;
runCLI(`generate @nx/nest:app ${nestapp} --linter=eslint`);
const lintResults = runCLI(`lint ${nestapp}`);
expect(lintResults).toContain('All files pass linting.');
updateFile(`apps/${nestapp}/src/assets/file.txt`, ``);
const jestResult = await runCLIAsync(`test ${nestapp}`);
expect(jestResult.combinedOutput).toContain(
'Test Suites: 2 passed, 2 total'
);
await runCLIAsync(`build ${nestapp}`);
checkFilesExist(
`dist/apps/${nestapp}/main.js`,
`dist/apps/${nestapp}/assets/file.txt`,
`dist/apps/${nestapp}/main.js.map`
);
const server = exec(`node ./dist/apps/${nestapp}/main.js`, {
cwd: tmpProjPath(),
});
expect(server).toBeTruthy();
// checking build
await new Promise((resolve) => {
server.stdout.on('data', async (data) => {
const message = data.toString();
if (message.includes(`Listening at http://localhost:${port}`)) {
const result = await getData(port);
expect(result.message).toEqual(`Welcome to ${nestapp}!`);
server.kill();
resolve(null);
}
});
});
// checking serve
const p = await runCommandUntil(
`serve ${nestapp} --port=${port}`,
(output) => {
process.stdout.write(output);
return output.includes(`Listening at http://localhost:${port}`);
}
);
const result = await getData(port);
expect(result.message).toEqual(`Welcome to ${nestapp}!`);
try {
await promisifiedTreeKill(p.pid, 'SIGKILL');
expect(await killPorts(port)).toBeTruthy();
} catch (err) {
expect(err).toBeFalsy();
}
}, 120000);
});
describe('Build Node apps', () => {
beforeEach(() => newProject());
afterEach(() => cleanupProject());
it('should generate a package.json with the `--generatePackageJson` flag', async () => {
const scope = newProject();
const packageManager = detectPackageManager(tmpProjPath());
const nestapp = uniq('nestapp');
runCLI(`generate @nx/nest:app ${nestapp} --linter=eslint`);
await runCLIAsync(`build ${nestapp} --generatePackageJson`);
checkFilesExist(`dist/apps/${nestapp}/package.json`);
checkFilesExist(
`dist/apps/${nestapp}/${getLockFileName(
detectPackageManager(tmpProjPath())
)}`
);
const rootPackageJson = JSON.parse(readFile(`package.json`));
const packageJson = JSON.parse(
readFile(`dist/apps/${nestapp}/package.json`)
);
expect(packageJson).toEqual(
expect.objectContaining({
main: 'main.js',
name: expect.any(String),
version: '0.0.1',
})
);
expect(
satisfies(
packageJson.dependencies['@nestjs/common'],
rootPackageJson.dependencies['@nestjs/common']
)
).toBeTruthy();
expect(
satisfies(
packageJson.dependencies['@nestjs/core'],
rootPackageJson.dependencies['@nestjs/core']
)
).toBeTruthy();
expect(
satisfies(
packageJson.dependencies['reflect-metadata'],
rootPackageJson.dependencies['reflect-metadata']
)
).toBeTruthy();
expect(
satisfies(
packageJson.dependencies['rxjs'],
rootPackageJson.dependencies['rxjs']
)
).toBeTruthy();
expect(
satisfies(
packageJson.dependencies['tslib'],
rootPackageJson.dependencies['tslib']
)
).toBeTruthy();
checkFilesExist(
`dist/apps/${nestapp}/${packageManagerLockFile[packageManager]}`
);
runCommand(`${getPackageManagerCommand().ciInstall}`, {
cwd: joinPathFragments(tmpProjPath(), 'dist/apps', nestapp),
});
const nodeapp = uniq('nodeapp');
runCLI(`generate @nx/node:app ${nodeapp} --bundler=webpack`);
const jslib = uniq('jslib');
runCLI(`generate @nx/js:lib ${jslib} --bundler=tsc`);
updateFile(
`apps/${nodeapp}/src/main.ts`,
`
import { ${jslib} } from '@${scope}/${jslib}';
console.log('Hello World!');
${jslib}();
`
);
const { combinedOutput: nodeCombinedOutput } = await runCLIAsync(
`build ${nodeapp} --generate-package-json`
);
expect(nodeCombinedOutput).not.toMatch(/Graph is not consistent/);
checkFilesExist(`dist/apps/${nodeapp}/package.json`);
checkFilesExist(
`dist/apps/${nodeapp}/${packageManagerLockFile[packageManager]}`
);
const nodeAppPackageJson = JSON.parse(
readFile(`dist/apps/${nodeapp}/package.json`)
);
expect(nodeAppPackageJson['dependencies']['tslib']).toBeTruthy();
runCommand(`${getPackageManagerCommand().ciInstall}`, {
cwd: joinPathFragments(tmpProjPath(), 'dist/apps', nestapp),
});
runCommand(`${getPackageManagerCommand().ciInstall}`, {
cwd: joinPathFragments(tmpProjPath(), 'dist/apps', nodeapp),
});
}, 1_000_000);
it('should remove previous output before building with the --deleteOutputPath option set', async () => {
const appName = uniq('app');
runCLI(`generate @nx/node:app ${appName} --no-interactive`);
// deleteOutputPath should default to true
createFile(`dist/apps/${appName}/_should_remove.txt`);
createFile(`dist/apps/_should_not_remove.txt`);
checkFilesExist(
`dist/apps/${appName}/_should_remove.txt`,
`dist/apps/_should_not_remove.txt`
);
runCLI(`build ${appName} --outputHashing none`); // no explicit deleteOutputPath option set
checkFilesDoNotExist(`dist/apps/${appName}/_should_remove.txt`);
checkFilesExist(`dist/apps/_should_not_remove.txt`);
// Explicitly set `deleteOutputPath` to true
createFile(`dist/apps/${appName}/_should_remove.txt`);
createFile(`dist/apps/_should_not_remove.txt`);
checkFilesExist(
`dist/apps/${appName}/_should_remove.txt`,
`dist/apps/_should_not_remove.txt`
);
runCLI(`build ${appName} --outputHashing none --deleteOutputPath`);
checkFilesDoNotExist(`dist/apps/${appName}/_should_remove.txt`);
checkFilesExist(`dist/apps/_should_not_remove.txt`);
// Explicitly set `deleteOutputPath` to false
createFile(`dist/apps/${appName}/_should_keep.txt`);
createFile(`dist/apps/_should_keep.txt`);
runCLI(`build ${appName} --deleteOutputPath=false --outputHashing none`);
checkFilesExist(`dist/apps/${appName}/_should_keep.txt`);
checkFilesExist(`dist/apps/_should_keep.txt`);
}, 120000);
describe('NestJS', () => {
it('should have plugin output if specified in `tsPlugins`', async () => {
newProject();
const nestapp = uniq('nestapp');
runCLI(`generate @nx/nest:app ${nestapp} --linter=eslint`);
packageInstall('@nestjs/swagger', undefined, '^6.0.0');
updateProjectConfig(nestapp, (config) => {
config.targets.build.options.tsPlugins = ['@nestjs/swagger/plugin'];
return config;
});
updateFile(
`apps/${nestapp}/src/app/foo.dto.ts`,
`
export class FooDto {
foo: string;
bar: number;
}`
);
updateFile(
`apps/${nestapp}/src/app/app.controller.ts`,
`
import { Controller, Get } from '@nestjs/common';
import { FooDto } from './foo.dto';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getData() {
return this.appService.getData();
}
@Get('foo')
getFoo(): Promise<FooDto> {
return Promise.resolve({
foo: 'foo',
bar: 123
})
}
}`
);
await runCLIAsync(`build ${nestapp}`);
const mainJs = readFile(`dist/apps/${nestapp}/main.js`);
expect(mainJs).toContain('FooDto');
expect(mainJs).toContain('_OPENAPI_METADATA_FACTORY');
}, 300000);
});
});
describe('nest libraries', function () {
beforeEach(() => newProject());
it('should be able to generate a nest library', async () => {
const nestlib = uniq('nestlib');
runCLI(`generate @nx/nest:lib ${nestlib}`);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('All files pass linting.');
const testResults = runCLI(`test ${nestlib}`);
expect(testResults).toContain(
`Successfully ran target test for project ${nestlib}`
);
}, 60000);
it('should be able to generate a nest library w/ service', async () => {
const nestlib = uniq('nestlib');
runCLI(`generate @nx/nest:lib ${nestlib} --service`);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('All files pass linting.');
const jestResult = await runCLIAsync(`test ${nestlib}`);
expect(jestResult.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
}, 200000);
it('should be able to generate a nest library w/ controller', async () => {
const nestlib = uniq('nestlib');
runCLI(`generate @nx/nest:lib ${nestlib} --controller`);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('All files pass linting.');
const jestResult = await runCLIAsync(`test ${nestlib}`);
expect(jestResult.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
}, 200000);
it('should be able to generate a nest library w/ controller and service', async () => {
const nestlib = uniq('nestlib');
runCLI(`generate @nx/nest:lib ${nestlib} --controller --service`);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('All files pass linting.');
const jestResult = await runCLIAsync(`test ${nestlib}`);
expect(jestResult.combinedOutput).toContain(
'Test Suites: 2 passed, 2 total'
);
}, 200000);
it('should have plugin output if specified in `transformers`', async () => {
newProject();
const nestlib = uniq('nestlib');
runCLI(`generate @nx/nest:lib ${nestlib} --buildable`);
packageInstall('@nestjs/swagger', undefined, '~6.3.0');
updateProjectConfig(nestlib, (config) => {
config.targets.build.options.transformers = [
{
name: '@nestjs/swagger/plugin',
options: {
dtoFileNameSuffix: ['.model.ts'],
},
},
];
return config;
});
updateFile(
`libs/${nestlib}/src/lib/foo.model.ts`,
`
export class FooModel {
foo?: string;
bar?: number;
}`
);
await runCLIAsync(`build ${nestlib}`);
const fooModelJs = readFile(`dist/libs/${nestlib}/src/lib/foo.model.js`);
expect(stripIndents`${fooModelJs}`).toContain(
stripIndents`
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FooModel = void 0;
const openapi = require("@nestjs/swagger");
class FooModel {
static _OPENAPI_METADATA_FACTORY() {
return { foo: { required: false, type: () => String }, bar: { required: false, type: () => Number } };
}
}
exports.FooModel = FooModel;
//# sourceMappingURL=foo.model.js.map
`
);
}, 300000);
it('should run default jest tests', async () => {
await expectJestTestsToPass('@nx/node:lib');
}, 100000);
});