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

chore(cli): refactor checks into decorators #121

Closed
wants to merge 8 commits into from
Closed

Conversation

louis-bompart
Copy link
Collaborator

@louis-bompart louis-bompart commented Mar 30, 2021

  • Split the Node and NPX checks from the React command and into preconditions
  • Refactor the Authentication decorator into a precondition
  • Added a Preconditions checker decorator.

Unit Tested 💯
image


CDX-203

@github-actions
Copy link
Contributor

Pull Request Report

PR Title

✅ Title follows the conventional commit spec.

@@ -19,6 +19,7 @@
"cli-ux": "^5.5.1",
"coveo.analytics": "^2.18.4",
"create-react-app": "^4.0.3",
"dedent": "^0.7.0",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat library to avoid oopsies in indentation with template string
https://www.npmjs.com/package/dedent

"@types/fs-extra": "^9.0.6",
"@types/jest": "^26.0.22",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To have describe in here.

"@types/node": "^10",
"chai": "^4.2.0",
"jest": "^26.6.3",
"patch-package": "^6.2.2",
"rimraf": "^3.0.2",
"ts-jest": "^26.5.1",
"ts-node": "^8",
"typescript": "^3.3"
"typescript": "4.2.3"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be reverted:
I wanted to migrate the tsconfig to a composite project to split the test tsconfig from the source tsconfig (to avoid having describe everywhere), but was unable because of oclif/oclif#299.
Oclif went hay-wire when I tried to split it.

@@ -75,12 +75,12 @@ describe('auth:login', () => {
test
.command(['auth:login', '-e', 'foo'])
.catch(/Expected --environment=foo/)
.it('reject invalid environment');
.it('reject invalid environment', async () => {});
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noted timing issue and promise timing out here.
I'm honestly not too sure of what's going in in this tests, the syntax is really painful to read.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the syntax is really painful to read.

What it does:

* start a test
* execute the command auth:login -e foo -> try to login in environment "foo" which is not a valid environment
* catch the error, and regex match on `/Expected --environment=foo/` on the output (stderr)
* .it(the description of the test)

https://oclif.io/docs/testing and https://github.com/oclif/fancy-test for more documentation

That's how all the UT for the commands themselves will look like, so better get used to it ;)


static description = `Create a Coveo Headless-powered search page with the React web framework. See ${linkToReadme}`;
static description =
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the page about the command was really shallow after reworking it, I removed the link.

Comment on lines -4 to -10
declare global {
namespace NodeJS {
interface Global {
config: IConfig;
}
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put the typing shenanigan in a declaration file to keep things tidy.

Comment on lines +27 to +30
expect(fakeCommand.warn).toHaveBeenCalledWith(dedent`
Required version invalid: "foo".
Please report this error to Coveo: https://github.com/coveo/cli/issues/new
`);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See below for more explanation ;)

};

await expect(
IsNodeVersionAbove('0.0.1')((fakeCommand as unknown) as Command)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prolly could do a bit cleaner than the double cast with some Jest-pirouette, but I didn't think it was worth the hassle.

Comment on lines +15 to +18
target.warn(dedent`
Required version invalid: "${requiredVersion}".
Please report this error to Coveo: https://github.com/coveo/cli/issues/new
`);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this warning is triggered, there's a big chance that it's not the user machine that ain't properly set up, but that we messed up and didn't call properly the function.

Hopefully, this code will never run, but if it does, at least the user will know what to do (contact us so we can fix it).

Comment on lines +53 to +64
export function warnHowToInstallNode(target: Command) {
target.warn(dedent`
Please visit ${linkToReadme} for more detailed installation information.
`);
}

export function isMissingExecutable(output: SpawnProcessOutput) {
return (
output.exitCode !== null &&
Math.abs(output.exitCode) === constants.errno.ENOENT
);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be refactorized on a subsequent PR as utils, for now, that was not worth it. (when introducing decorator for Angular).

) => Boolean | Promise<Boolean>;

export function Preconditions(...preconditions: PreconditionFunction[]) {
return function (
Copy link
Collaborator Author

@louis-bompart louis-bompart Mar 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fun anecdote: don't put async here, typings get messy (rightfully so)

Copy link
Contributor

@y-lakhdar y-lakhdar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice Job

@@ -75,12 +75,12 @@ describe('auth:login', () => {
test
.command(['auth:login', '-e', 'foo'])
.catch(/Expected --environment=foo/)
.it('reject invalid environment');
.it('reject invalid environment', async () => {});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the syntax is really painful to read.

What it does:

* start a test
* execute the command auth:login -e foo -> try to login in environment "foo" which is not a valid environment
* catch the error, and regex match on `/Expected --environment=foo/` on the output (stderr)
* .it(the description of the test)

https://oclif.io/docs/testing and https://github.com/oclif/fancy-test for more documentation

That's how all the UT for the commands themselves will look like, so better get used to it ;)

describe('if all preconditions succeed', () => {
it('should executes preconditions the original command', async () => {
let counter = 0;
const orderChecker = jest.fn();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you put quite a lot of emphasis in the tests on "the order is respected".

Is that really an important/meaningful thing to test ?

I guess it can be, if we have preconditions that depends on one another, but right now it does not seem to be the case that we have such a need.

The most important thing to verify is that the original function is only called after all preconditions had a chance to execute.

Maybe we could simplify the test to only focus on this type of verification ?

});

describe('if one precondition failed', () => {
it('should executes all the preconditions prior to the failing one and nothing else', async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment as above, is that really something that we are interested in ?

If all preconditions are independant from one another, the order is not actually important, just that the "command" does not get executed if at least one precondition fails..

};

await expect(
IsNpxInstalled()((fakeCommand as unknown) as Command)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For readability's sake, I would add a function getFakeCommand(): Command {...} util function that does the cast + boilerplate.

That you could reuse for (I think?) all the tests in this file.

IsNpxInstalled()((fakeCommand as unknown) as Command)
).resolves.toBe(false);
expect(fakeCommand.warn).toHaveBeenCalledTimes(2);
expect(fakeCommand.warn).toHaveBeenCalledWith(dedent`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I would have a test that verifies the "exact same message contained in the command, just copy pasted".

Whenever we want to change a comma for the warning message, the tests will start to fail for no "good" reason.

I would focus on regex matching a couple important part: the readme link, a "high level check" on some important part of the message (eg: /some random error oh no/ or /a valid npx installation/, stuff like this).

The exact wording itself does not seem very important to validate with a UT.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants