Files
vercel/packages/cli/test/unit/commands/deploy.test.ts
Sean Massa 70c8b32cf0 [tests] Split CLI integration tests into chunks (#9688)
Welcome to the grand splitting of CLI integration tests:

- replaced `test-cli` test suite with `test-e2e`
- made some small fixes along the way
- split integration tests into 3 files
- moved shared logic to `packages/cli/test/helpers/*`
- simplified `_execa` / `execa` / `execute` usage into `execCli` and `exec`
    - simplified arguments required to make these work
- defaulted `execCLI` to set `NO_COLOR=1` to make assertions simpler in tests that aren't testing color/emoji support
- expanded functionality of `formatOutput` to handle error states
- centralized temp dir handling and cleanup
- enhanced `waitForPrompt` to:
    - more clearly show what it was waiting for
    - support waiting for regex, string, or a function that returns a boolean
    - show what was the most recent thing it saw
    - end early if the process it's monitoring exits
- removed some test pollution where unnecessary, shifted some into `beforeAll`
- renamed unit tests helper from `setupFixture` to `setupUnitFixture` to avoid confusion with the new shared helper `setupE2EFixture`


Some of this could be pulled out into a separate PR, but the feedback cycle is a slog, which this PR is helping to address. I'd be happy to discuss what could be pulled out, but I'd also be happy to get the whole thing through.

---

Wait for prompt failures:

<img width="939" alt="CleanShot 2023-03-27 at 10 24 21@2x" src="https://user-images.githubusercontent.com/41545/227987773-a3582549-32f9-4131-8a35-7be7cc265b66.png">

---

Current Timing:

```
Tests / test-e2e (vercel, 1, ubuntu-latest) (pull_request) Successful in 3m
Tests / test-e2e (vercel, 2, ubuntu-latest) (pull_request) Successful in 8m
Tests / test-e2e (vercel, 3, ubuntu-latest) (pull_request) Successful in 8m
```

---

Before merge, I'll mark the original `CLI` integration test suite as no longer required.
2023-03-29 07:44:42 +00:00

321 lines
11 KiB
TypeScript

import bytes from 'bytes';
import fs from 'fs-extra';
import { join } from 'path';
import { randomBytes } from 'crypto';
import { fileNameSymbol } from '@vercel/client';
import { client } from '../../mocks/client';
import deploy from '../../../src/commands/deploy';
import { setupUnitFixture } from '../../helpers/setup-unit-fixture';
import { defaultProject, useProject } from '../../mocks/project';
import { useTeams } from '../../mocks/team';
import { useUser } from '../../mocks/user';
describe('deploy', () => {
it('should reject deploying a single file', async () => {
client.setArgv('deploy', __filename);
const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput(
`Error: Support for single file deployments has been removed.\nLearn More: https://vercel.link/no-single-file-deployments\n`
);
await expect(exitCodePromise).resolves.toEqual(1);
});
it('should reject deploying multiple files', async () => {
client.setArgv('deploy', __filename, join(__dirname, 'inspect.test.ts'));
const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput(
`Error: Can't deploy more than one path.\n`
);
await expect(exitCodePromise).resolves.toEqual(1);
});
it('should reject deploying a directory that does not exist', async () => {
client.setArgv('deploy', 'does-not-exists');
const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput(
`Error: The specified file or directory "does-not-exists" does not exist.\n`
);
await expect(exitCodePromise).resolves.toEqual(1);
});
it('should reject deploying when `--prebuilt` is used and `vc build` failed before Builders', async () => {
const cwd = setupUnitFixture('build-output-api-failed-before-builds');
client.setArgv('deploy', cwd, '--prebuilt');
const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput(
'> Prebuilt deployment cannot be created because `vercel build` failed with error:\n\nError: The build failed (top-level)\n'
);
await expect(exitCodePromise).resolves.toEqual(1);
});
it('should reject deploying when `--prebuilt` is used and `vc build` failed within a Builder', async () => {
const cwd = setupUnitFixture('build-output-api-failed-within-build');
client.setArgv('deploy', cwd, '--prebuilt');
const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput(
'> Prebuilt deployment cannot be created because `vercel build` failed with error:\n\nError: The build failed within a Builder\n'
);
await expect(exitCodePromise).resolves.toEqual(1);
});
it('should reject deploying a directory that does not contain ".vercel/output" when `--prebuilt` is used', async () => {
client.setArgv('deploy', __dirname, '--prebuilt');
const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput(
'Error: The "--prebuilt" option was used, but no prebuilt output found in ".vercel/output". Run `vercel build` to generate a local build.\n'
);
await expect(exitCodePromise).resolves.toEqual(1);
});
it('should reject deploying a directory that was built with a different target environment when `--prebuilt --prod` is used on "preview" output', async () => {
const cwd = setupUnitFixture('build-output-api-preview');
useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
id: 'build-output-api-preview',
name: 'build-output-api-preview',
});
client.setArgv('deploy', cwd, '--prebuilt', '--prod');
const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput(
'Error: The "--prebuilt" option was used with the target environment "production",' +
' but the prebuilt output found in ".vercel/output" was built with target environment "preview".' +
' Please run `vercel --prebuilt`.\n' +
'Learn More: https://vercel.link/prebuilt-environment-mismatch\n'
);
await expect(exitCodePromise).resolves.toEqual(1);
});
it('should reject deploying a directory that was built with a different target environment when `--prebuilt` is used on "production" output', async () => {
const cwd = setupUnitFixture('build-output-api-production');
useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
id: 'build-output-api-preview',
name: 'build-output-api-preview',
});
client.setArgv('deploy', cwd, '--prebuilt');
const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput(
'Error: The "--prebuilt" option was used with the target environment "preview",' +
' but the prebuilt output found in ".vercel/output" was built with target environment "production".' +
' Please run `vercel --prebuilt --prod`.\n' +
'Learn More: https://vercel.link/prebuilt-environment-mismatch\n'
);
await expect(exitCodePromise).resolves.toEqual(1);
});
it('should reject deploying "version: 1"', async () => {
client.setArgv('deploy');
client.localConfig = {
[fileNameSymbol]: 'vercel.json',
version: 1,
};
const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput(
'Error: The value of the `version` property within vercel.json can only be `2`.\n'
);
await expect(exitCodePromise).resolves.toEqual(1);
});
it('should reject deploying "version: {}"', async () => {
client.setArgv('deploy');
client.localConfig = {
[fileNameSymbol]: 'vercel.json',
// @ts-ignore
version: {},
};
const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput(
'Error: The `version` property inside your vercel.json file must be a number.\n'
);
await expect(exitCodePromise).resolves.toEqual(1);
});
it('should send a tgz file when `--archive=tgz`', async () => {
const cwd = setupUnitFixture('commands/deploy/archive');
const originalCwd = process.cwd();
try {
process.chdir(cwd);
const user = useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
name: 'archive',
id: 'archive',
});
let body: any;
client.scenario.post(`/v13/deployments`, (req, res) => {
body = req.body;
res.json({
creator: {
uid: user.id,
username: user.username,
},
id: 'dpl_archive_test',
});
});
client.scenario.get(`/v13/deployments/dpl_archive_test`, (req, res) => {
res.json({
creator: {
uid: user.id,
username: user.username,
},
id: 'dpl_archive_test',
readyState: 'READY',
aliasAssigned: true,
alias: [],
});
});
client.scenario.get(
`/v10/now/deployments/dpl_archive_test`,
(req, res) => {
res.json({
creator: {
uid: user.id,
username: user.username,
},
id: 'dpl_archive_test',
readyState: 'READY',
aliasAssigned: true,
alias: [],
});
}
);
client.setArgv('deploy', '--archive=tgz');
const exitCode = await deploy(client);
expect(exitCode).toEqual(0);
expect(body?.files?.length).toEqual(1);
expect(body?.files?.[0].file).toEqual('.vercel/source.tgz');
} finally {
process.chdir(originalCwd);
}
});
it('should upload missing files', async () => {
const cwd = setupUnitFixture('commands/deploy/archive');
const originalCwd = process.cwd();
// Add random 1mb file
await fs.writeFile(join(cwd, 'data'), randomBytes(bytes('1mb')));
try {
process.chdir(cwd);
const user = useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
name: 'archive',
id: 'archive',
});
let body: any;
let fileUploaded = false;
client.scenario.post(`/v13/deployments`, (req, res) => {
if (fileUploaded) {
body = req.body;
res.json({
creator: {
uid: user.id,
username: user.username,
},
id: 'dpl_archive_test',
});
} else {
const sha = req.body.files[0].sha;
res.status(400).json({
error: {
code: 'missing_files',
message: 'Missing files',
missing: [sha],
},
});
}
});
client.scenario.post('/v2/files', (req, res) => {
// Wait for file to be finished uploading
req.on('data', () => {
// Noop
});
req.on('end', () => {
fileUploaded = true;
res.end();
});
});
client.scenario.get(`/v13/deployments/dpl_archive_test`, (req, res) => {
res.json({
creator: {
uid: user.id,
username: user.username,
},
id: 'dpl_archive_test',
readyState: 'READY',
aliasAssigned: true,
alias: [],
});
});
client.scenario.get(
`/v10/now/deployments/dpl_archive_test`,
(req, res) => {
res.json({
creator: {
uid: user.id,
username: user.username,
},
id: 'dpl_archive_test',
readyState: 'READY',
aliasAssigned: true,
alias: [],
});
}
);
// When stderr is not a TTY we expect 5 progress lines to be printed
client.stderr.isTTY = false;
client.setArgv('deploy', '--archive=tgz');
const uploadingLines: string[] = [];
client.stderr.on('data', data => {
if (data.startsWith('Uploading [')) {
uploadingLines.push(data);
}
});
client.stderr.resume();
const exitCode = await deploy(client);
expect(exitCode).toEqual(0);
expect(body?.files?.length).toEqual(1);
expect(body?.files?.[0].file).toEqual('.vercel/source.tgz');
expect(uploadingLines.length).toEqual(5);
expect(
uploadingLines[0].startsWith('Uploading [--------------------]')
).toEqual(true);
expect(
uploadingLines[1].startsWith('Uploading [=====---------------]')
).toEqual(true);
expect(
uploadingLines[2].startsWith('Uploading [==========----------]')
).toEqual(true);
expect(
uploadingLines[3].startsWith('Uploading [===============-----]')
).toEqual(true);
expect(
uploadingLines[4].startsWith('Uploading [====================]')
).toEqual(true);
} finally {
process.chdir(originalCwd);
}
});
});