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

Add lint + test #20

Merged
merged 7 commits into from
Feb 20, 2024
Merged
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
64 changes: 64 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
module.exports = {
env: {
browser: false,
es2021: true,
},
// Note that order here matters
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:@typescript-eslint/recommended",
"prettier",
],
rules: {
// Allow empty generators
"require-yield": "off",
// Assume variables prefixed with "_" are intentionally unused
"@typescript-eslint/no-unused-vars": [
"warn",
{ argsIgnorePattern: "_.*", varsIgnorePattern: "_.*" },
],
// Explicit any should be used judiciously to avoid undue wrestling with TS
"@typescript-eslint/no-explicit-any": "off",
// no-empty-function is just pedantic
"@typescript-eslint/no-empty-function": "off",
// TODO: fix these in the future (all variations of using `any`)
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-return": "off",
// We may want to improve namespaces at some point, but at this juncture they make
// our type system more readable
"@typescript-eslint/no-namespace": "off",
// This is just so we don't accidentally render '[object: Object]'
"@typescript-eslint/restrict-template-expressions": [
"warn",
{ allowNumber: true, allowNullish: true, allowBoolean: true },
],
// This prevents trivial implementations of asynchronous interfaces
"@typescript-eslint/require-await": "off",
// For code readability; do not apply to intersections as then order matters
"@typescript-eslint/sort-type-constituents": [
"warn",
{ checkIntersections: false },
],
"@typescript-eslint/unbound-method": ["error", { ignoreStatic: true }],
// Allow console.log() statements
"no-console": "off",
},
ignorePatterns: [
".eslintrc.js",
"prettier.config.js",
"jest.config.js",
"public/**",
"build/**",
"node_modules/**",
],
overrides: [],
parserOptions: {
ecmaVersion: "latest",
project: ["./tsconfig.json"],
sourceType: "module",
},
};
37 changes: 37 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
on:
pull_request: {}
push:
branches: [main]

name: Lint

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ^18.6.0
cache: yarn
cache-dependency-path: |
yarn.lock
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: |
echo dir="$(yarn cache dir)" >> $GITHUB_OUTPUT
echo version="$(yarn cache dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: |
**/node_modules
${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ steps.yarn-cache-dir-path.outputs.version }}-${{ hashFiles('**/yarn.lock') }}
- name: Yarn install
run: yarn install
- name: Verify format
run: yarn prettier --check .
- name: Lint
run: yarn run eslint --max-warnings 0 .
35 changes: 35 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
on:
pull_request: {}
push:
branches: [main]

name: Test

jobs:
test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ^18.6.0
cache: yarn
cache-dependency-path: |
yarn.lock
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: |
echo dir="$(yarn cache dir)" >> $GITHUB_OUTPUT
echo version="$(yarn cache dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: |
**/node_modules
${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ steps.yarn-cache-dir-path.outputs.version }}-${{ hashFiles('**/yarn.lock') }}
- name: Yarn install
run: yarn install
- name: Run tests
run: yarn jest -w2
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tsconfig.json
2 changes: 1 addition & 1 deletion .prettierrc.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
plugins:
- '@trivago/prettier-plugin-sort-imports'
- "@trivago/prettier-plugin-sort-imports"
trailingComma: es5
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
"webRoot": "${workspaceFolder}"
}
]
}
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# p0cli
# p0cli
24 changes: 24 additions & 0 deletions __mocks__/firebase/firestore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { noop } from "lodash";

export const doc = jest.fn();

export const getFirestore = jest.fn().mockReturnValue({});

let snapshotCallbacks: ((snapshot: object) => void)[] = [];

export const onSnapshot = Object.assign(
jest.fn().mockImplementation((_doc, cb) => {
snapshotCallbacks.push(cb);
return noop;
}),
{
clear: (snapshotCallbacks = []),
trigger: (snap: any) => {
for (const cb of snapshotCallbacks) {
cb({ data: () => snap });
}
},
}
);

export const terminate = jest.fn();
6 changes: 6 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
modulePathIgnorePatterns: ["/build"],
};
6 changes: 3 additions & 3 deletions p0
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ process.emit = function (name, data, ...args) {
if (
name === `warning` &&
typeof data === `object` &&
data.name === `ExperimentalWarning`
&& data.message.startsWith(`The Fetch API is an experimental feature.`)
data.name === `ExperimentalWarning` &&
data.message.startsWith(`The Fetch API is an experimental feature.`)
) {
return false;
}
return originalEmit.apply(process, arguments);
};

require(`${__dirname}/build/index.js`).main();
require(`${__dirname}/build/index.js`).main();
13 changes: 12 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,22 @@
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.12",
"@types/jsdom": "^21.1.6",
"@types/lodash": "^4.14.202",
"@types/node": "^18.11.7",
"@types/yargs": "^17.0.13",
"prettier": "^3.2.4"
"@typescript-eslint/eslint-plugin": "^6.4.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard-with-typescript": "^43.0.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-promise": "^6.1.1",
"jest": "^29.7.0",
"prettier": "^3.2.4",
"ts-jest": "^29.1.2"
},
"scripts": {
"build": "tsc && cp -r public build/",
Expand Down
36 changes: 36 additions & 0 deletions src/commands/__tests__/__snapshots__/ls.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ls when error should print error message 1`] = `
{
"error": "p0 ls

List available resources

Commands:
p0 ls ssh <destination> Secure Shell (SSH) session

Options:
--help Show help [boolean]

Unknown argument: foo",
}
`;

exports[`ls when valid ls command should print list response 1`] = `
[
[
"Showing destinations:",
],
]
`;

exports[`ls when valid ls command should print list response 2`] = `
[
[
"instance-1",
],
[
"instance-2",
],
]
`;
57 changes: 57 additions & 0 deletions src/commands/__tests__/__snapshots__/request.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`request when error should print error message 1`] = `
{
"error": "p0 request

Request access to a resource using P0

Commands:
p0 request gcloud Google Cloud

Options:
--help Show help [boolean]
--reason Reason access is needed [string]
-w, --wait Block until the request is completed [boolean]

Unknown argument: foo",
}
`;

exports[`request when valid request command preexisting=false persistent=false should print request response 1`] = `
[
[
"a message",
],
]
`;

exports[`request when valid request command preexisting=false persistent=true should print request response 1`] = `
[
[
"a message",
],
]
`;

exports[`request when valid request command preexisting=true persistent=false should print request response 1`] = `
[
[
"a message",
],
]
`;

exports[`request when valid request command should wait for access 1`] = `
[
[
"a message",
],
[
"Will wait up to 5 minutes for this request to complete...",
],
[
"Your request was approved",
],
]
`;
63 changes: 63 additions & 0 deletions src/commands/__tests__/ls.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { fetchCommand } from "../../drivers/api";
import { lsCommand } from "../ls";
import yargs from "yargs";

jest.mock("../../drivers/api");
jest.mock("../../drivers/auth");

const mockFetchCommand = fetchCommand as jest.Mock;
const stdout = jest.spyOn(global.console, "log");
const stderr = jest.spyOn(global.console, "error");

describe("ls", () => {
describe("when valid ls command", () => {
const command = "ls ssh destination";

beforeAll(() => {
mockFetchCommand.mockResolvedValue({
ok: true,
term: "",
arg: "destination",
items: ["instance-1", "instance-2"],
});
});

it("should print list response", async () => {
await lsCommand(yargs).parse(command);
expect(stderr.mock.calls).toMatchSnapshot();
expect(stdout.mock.calls).toMatchSnapshot();
});
});

describe("when error", () => {
const command = "ls foo";

beforeAll(() => {
mockFetchCommand.mockResolvedValue({
error: `p0 ls

List available resources

Commands:
p0 ls ssh <destination> Secure Shell (SSH) session

Options:
--help Show help [boolean]

Unknown argument: foo`,
});
});

it("should print error message", async () => {
let error: any;
try {
await lsCommand(yargs)
.fail((_, err) => (error = err))
.parse(command);
} catch (thrown: any) {
error = thrown;
}
expect(error).toMatchSnapshot();
});
});
});
Loading
Loading