Skip to content

Commit

Permalink
chore: fix all the types & the rule runner thing
Browse files Browse the repository at this point in the history
  • Loading branch information
boneskull committed May 20, 2024
1 parent 5cb319e commit c5eea11
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 209 deletions.
110 changes: 88 additions & 22 deletions packages/plugin-default/test/e2e/rules/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,29 @@
import {head} from 'lodash';
import {type SmokerOptions} from 'midnight-smoker';
import {
ComponentKinds,
DEFAULT_PKG_MANAGER_BIN,
DEFAULT_PKG_MANAGER_VERSION,
} from 'midnight-smoker/constants';
import {PluginInitError, fromUnknownError} from 'midnight-smoker/error';
import {
RuleMachine,
createActor,
toPromise,
type CheckOutput,
} from 'midnight-smoker/machine';
import {RuleMachine, createActor, toPromise} from 'midnight-smoker/machine';
import {
PluginMetadata,
createPluginAPI,
type PluginFactory,
type PluginRegistry,
} from 'midnight-smoker/plugin';
import {
DEFAULT_RULE_SEVERITY,
getDefaultRuleOptions,
type RuleDefSchemaValue,
type RuleOptions,
type RuleResultFailed,
type RuleResultOk,
type SomeRuleDef,
type SomeRuleOptions,
} from 'midnight-smoker/rule';
import {type FileManager, type FileManagerOpts} from 'midnight-smoker/util';
import {createPluginAPI} from '../../../../midnight-smoker/src/plugin/create-plugin-api';
import {getDefaultRuleOptions} from '../../../../midnight-smoker/src/rule/create-rule-options';

/**
* Runs a {@link Rule} against a fixture.
Expand Down Expand Up @@ -58,7 +56,58 @@ export interface CreateRuleRunnerOptions {
smokerOpts?: SmokerOptions;
}

export async function createRuleRunner(fn: PluginFactory) {
export type NamedRuleRunner = (
installPath: string,
opts?: SomeRuleOptions,
) => Promise<RuleResultOk | RuleResultFailed[]>;

export type RuleRunner = (
name: string,
installPath: string,
opts?: SomeRuleOptions,
) => Promise<RuleResultOk | RuleResultFailed[]>;

/**
* Factory function which creates a {@link NamedRuleRunner}.
*
* Since a {@link PluginFactory} can define multiple rules, this function allows
* you to specify which rule to run.
*
* If you wish to test multiple rules, omit the `name` parameter; you will
* receive a {@link RuleRunner} instead.
*
* If you have a `RuleDef` instead of a `PluginFactory`, use {@link runRule}
* instead.
*
* @param fn Plugin factory function
* @param name Rule name
* @returns Rule runner function (can only run the rule specified by the `name`
* parameter)
*/
export async function createRuleRunner(
fn: PluginFactory,
name: string,
): Promise<NamedRuleRunner>;

/**
* Factory function which creates a {@link RuleRunner}.
*
* Since a {@link PluginFactory} can define multiple rules, this function allows
* you to run any rule defined by the plugin.
*
* If you only wish to test a single rule, supply a `name` property for the
* second parameter.
*
* If you have a `RuleDef` instead of a `PluginFactory`, use {@link runRule}
* instead.
*
* @param fn Plugin factory function
* @returns Rule runner function (can run any rule defined by the plugin
* factory)
*/
export async function createRuleRunner(fn: PluginFactory): Promise<RuleRunner>;

export async function createRuleRunner(fn: PluginFactory, name?: string) {
const ruleDefs: Map<string, SomeRuleDef> = new Map();
const metadata = PluginMetadata.createTransient('test-plugin');
const pluginApi = createPluginAPI(
Expand All @@ -76,26 +125,39 @@ export async function createRuleRunner(fn: PluginFactory) {
throw new PluginInitError(fromUnknownError(err), metadata);
}

return (name: string, installPath: string, opts?: SomeRuleOptions) => {
if (name) {
const def = ruleDefs.get(name);
if (!def) {
throw new ReferenceError(`RuleDef "${name}" not found`);
}
return async (installPath: string, opts?: SomeRuleOptions) =>
runRule(def, installPath, opts);
}

return async (name: string, installPath: string, opts?: SomeRuleOptions) => {
const def = ruleDefs.get(name);
if (!def) {
throw new ReferenceError(`Rule "${name}" not found`);
throw new ReferenceError(`RuleDef "${name}" not found`);
}
return applyRule(def, installPath, opts);
return runRule(def, installPath, opts);
};
}

export function applyRule<T extends SomeRuleDef>(
def: T,
installPath: string,
opts?: RuleOptions<T['schema']>,
): Promise<CheckOutput[]>;

export function applyRule<T extends SomeRuleDef>(
/**
* Given a rule definition, runs the rule against a package dir with the
* specified options (if given).
*
* @param def Rule definition
* @param installPath Path to package dir to test
* @param opts Rule-specific options
* @returns A `CheckOutput` object
*/
export async function runRule<T extends SomeRuleDef>(
def: T,
installPath: string,
opts?: RuleOptions<T['schema']>,
): Promise<CheckOutput[]> {
): Promise<RuleResultOk | RuleResultFailed[]> {
const plan = 1;
const defaultOpts = getDefaultRuleOptions(def.schema as RuleDefSchemaValue);
const someConfig = {
opts: {...defaultOpts, ...opts},
Expand All @@ -106,7 +168,7 @@ export function applyRule<T extends SomeRuleDef>(
def,
ruleId: def.name,
config: someConfig,
plan: 1,
plan,
},
});
ruleMachine.send({
Expand All @@ -129,5 +191,9 @@ export function applyRule<T extends SomeRuleDef>(
pkgName: '',
},
});
return toPromise(ruleMachine);
const output = await toPromise(ruleMachine);
if (output.length !== plan) {
throw new Error(`Expected exactly ${plan} result(s)`);
}
return head(output)!.result;
}
24 changes: 11 additions & 13 deletions packages/plugin-default/test/e2e/rules/no-banned-files.spec.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
import {registerRule} from '@midnight-smoker/test-util';
import {PluginRegistry} from 'midnight-smoker/plugin';
import {RuleSeverities, type SomeRule} from 'midnight-smoker/rule';
import {RuleSeverities} from 'midnight-smoker/rule';
import {normalize} from 'node:path';
import unexpected from 'unexpected';
import noBannedFilesDef from '../../../src/rules/no-banned-files';
import {applyRule} from './helpers';
import noBannedFiles from '../../../src/rules/no-banned-files';
import {createRuleRunner, type NamedRuleRunner} from './helpers';

const expect = unexpected.clone();

describe('@midnight-smoker/plugin-default', function () {
let noBannedFiles: SomeRule;

describe('rule', function () {
describe('no-banned-files', function () {
let runRule: NamedRuleRunner;
const name = 'no-banned-files';

before(async function () {
const registry = PluginRegistry.create();
noBannedFiles = await registerRule(registry, noBannedFilesDef);
runRule = await createRuleRunner(noBannedFiles, name);
});

describe('when the package contains a banned file', function () {
const fixture = normalize(`${__dirname}/fixture/no-banned-files`);

it('should return a failure for each banned file', async function () {
await expect(
applyRule(noBannedFiles, fixture),
runRule(fixture),
'to be fulfilled with value satisfying',
[
{
rule: noBannedFiles.toJSON(),
rule: name,
message: 'Banned file found: id_rsa (Private SSH key)',
context: {
pkgJson: expect.it('to be an object'),
Expand All @@ -51,11 +49,11 @@ describe('@midnight-smoker/plugin-default', function () {

it('should allow additional files to be banned', async function () {
await expect(
applyRule(noBannedFiles, fixture, config),
runRule(fixture, config),
'to be fulfilled with value satisfying',
[
{
rule: noBannedFiles.toJSON(),
rule: name,
message:
'Banned file found: anarchist-cookbook.txt (per custom deny list)',
context: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import {registerRule} from '@midnight-smoker/test-util';
import {PluginRegistry} from 'midnight-smoker/plugin';
import {RuleSeverities, type SomeRule} from 'midnight-smoker/rule';
import {RuleSeverities} from 'midnight-smoker/rule';
import {normalize} from 'node:path';
import unexpected from 'unexpected';
import noMissingEntryPointDef from '../../../src/rules/no-missing-entry-point';
import {applyRule} from './helpers';
import noMissingEntryPoint from '../../../src/rules/no-missing-entry-point';
import {createRuleRunner, type NamedRuleRunner} from './helpers';

const expect = unexpected.clone();

describe('@midnight-smoker/plugin-default', function () {
let noMissingEntryPoint: SomeRule;

describe('rule', function () {
describe('no-missing-entry-point', function () {
let runRule: NamedRuleRunner;
const name = 'no-missing-entry-point';

before(async function () {
const registry = PluginRegistry.create();
noMissingEntryPoint = await registerRule(
registry,
noMissingEntryPointDef,
);
runRule = await createRuleRunner(noMissingEntryPoint, name);
});

describe('when the package is an ESM package', function () {
Expand All @@ -27,11 +22,7 @@ describe('@midnight-smoker/plugin-default', function () {
);

it('should not return a failure', async function () {
await expect(
applyRule(noMissingEntryPoint, fixture),
'to be fulfilled with',
undefined,
);
await expect(runRule(fixture), 'to be fulfilled with', undefined);
});
});

Expand All @@ -43,11 +34,11 @@ describe('@midnight-smoker/plugin-default', function () {

it('should return a failure', async function () {
await expect(
applyRule(noMissingEntryPoint, fixture),
runRule(fixture),
'to be fulfilled with value satisfying',
[
{
rule: noMissingEntryPoint.toJSON(),
rule: name,
message:
'No entry point found for package "no-missing-entry-point"; file from field "main" unreadable at path: index.js',
context: {
Expand All @@ -68,11 +59,7 @@ describe('@midnight-smoker/plugin-default', function () {
);

it('should not return a failure', async function () {
await expect(
applyRule(noMissingEntryPoint, fixture),
'to be fulfilled with',
undefined,
);
await expect(runRule(fixture), 'to be fulfilled with', undefined);
});
});
});
Expand All @@ -85,11 +72,11 @@ describe('@midnight-smoker/plugin-default', function () {

it('should return a failure', async function () {
await expect(
applyRule(noMissingEntryPoint, fixture),
runRule(fixture),
'to be fulfilled with value satisfying',
[
{
rule: noMissingEntryPoint.toJSON(),
rule: name,
message:
'No entry point found for package "no-missing-entry-point"; file from field "main" unreadable at path: index.js',
context: {
Expand All @@ -111,7 +98,7 @@ describe('@midnight-smoker/plugin-default', function () {

it('should not return a failure', async function () {
await expect(
applyRule(noMissingEntryPoint, fixture),
runRule(fixture),
'to be fulfilled with value satisfying',
expect.it('to be undefined'),
);
Expand Down
Loading

0 comments on commit c5eea11

Please sign in to comment.