Skip to content

Commit

Permalink
feat: allow adding of steps (#72)
Browse files Browse the repository at this point in the history
* added ability to mock step using index of that step and added ability to insert steps instead of replacing them

* updated test cases

* updated docs

* fix indexing problem
  • Loading branch information
shubhbapna authored Feb 1, 2024
1 parent 2949776 commit 0b6f849
Show file tree
Hide file tree
Showing 4 changed files with 291 additions and 16 deletions.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -404,12 +404,25 @@ Schema for `mockSteps`
{
run: "locates the step using the run field"
mockWith: "command or a new step as JSON to replace the given step with"
} |
{
index: "locates the step using the index (0 indexed) in the steps array of the workflow"
mockWith: "command or a new step as JSON to replace the given step with"
} |
{
before: "index of the step or the id/name/run/uses of the step before which you want to insert a step"
mockWith: "a new step as JSON to be added before the given step"
} |
{
after: "index of the step or the id/name/run/uses of the step after which you want to insert a step"
mockWith: "a new step as JSON to be added after the given step"
}
)[]
}
```

NOTE: Please use `MockGithub` to run the workflow in a clean safe github repository so that any changes made to the Workflow file are done in the test environment and not to the actual file.
**Important Notes**:
- Please use `MockGithub` to run the workflow in a clean safe github repository so that any changes made to the Workflow file are done in the test environment and not to the actual file.

#### Run result

Expand Down
75 changes: 63 additions & 12 deletions src/step-mocker/step-mocker.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import {
GithubWorkflow,
GithubWorkflowStep,
isStepIdentifierUsingAfter,
isStepIdentifierUsingBefore,
isStepIdentifierUsingBeforeOrAfter,
isStepIdentifierUsingId,
isStepIdentifierUsingIndex,
isStepIdentifierUsingName,
isStepIdentifierUsingRun,
isStepIdentifierUsingUses,
MockStep,
StepIdentifier,
StepIdentifierUsingAfter,
StepIdentifierUsingBefore,
} from "@aj/step-mocker/step-mocker.types";
import { existsSync, readFileSync, writeFileSync } from "fs";
import path from "path";
Expand All @@ -23,35 +29,64 @@ export class StepMocker {
async mock(mockSteps: MockStep) {
const filePath = this.getWorkflowPath();
const workflow = await this.readWorkflowFile(filePath);
for (const job of Object.keys(mockSteps)) {
for (const mockStep of mockSteps[job]) {
const { step, stepIndex } = this.locateStep(workflow, job, mockStep);
for (const jobId of Object.keys(mockSteps)) {
const stepsToAdd = [];
for (const mockStep of mockSteps[jobId]) {
const { step, stepIndex } = this.locateStep(workflow, jobId, mockStep);
if (step) {
if (typeof mockStep.mockWith === "string") {
this.updateStep(workflow, job, stepIndex, {
...step,
run: mockStep.mockWith,
uses: undefined,
});
if (isStepIdentifierUsingBeforeOrAfter(mockStep)) {
// need to adjust the step index if there were elements added previously
const adjustIndex: number = stepsToAdd.filter(s => s.stepIndex < stepIndex).length;
// we will only actually add the steps at the end so as to avoid indexing errors in subsequent add steps
stepsToAdd.push({jobId, stepIndex: stepIndex + adjustIndex, mockStep});
} else {
this.updateStep(workflow, job, stepIndex, mockStep.mockWith);
this.updateStep(workflow, jobId, stepIndex, mockStep);
}
} else {
throw new Error("Could not find step");
}
}
stepsToAdd.forEach(s => this.addStep(workflow, s.jobId, s.stepIndex, s.mockStep));
}
return this.writeWorkflowFile(filePath, workflow);
}

private addStep(
workflow: GithubWorkflow,
jobId: string,
stepIndex: number,
mockStep: StepIdentifierUsingAfter | StepIdentifierUsingBefore
) {
if (workflow.jobs[jobId]) {
let indexToInsertAt = stepIndex;
if (isStepIdentifierUsingBefore(mockStep)) {
indexToInsertAt = stepIndex <= 0 ? 0 : indexToInsertAt - 1;
} else {
indexToInsertAt =
stepIndex >= workflow.jobs[jobId].steps.length - 1
? workflow.jobs[jobId].steps.length
: indexToInsertAt + 1;
}
workflow.jobs[jobId].steps.splice(indexToInsertAt, 0, {...mockStep.mockWith});
}
}

private updateStep(
workflow: GithubWorkflow,
jobId: string,
stepIndex: number,
newStep: GithubWorkflowStep
mockStep: StepIdentifier
) {
if (workflow.jobs[jobId]) {
const oldStep = workflow.jobs[jobId].steps[stepIndex];
const newStep =
typeof mockStep.mockWith === "string"
? {
...oldStep,
run: mockStep.mockWith,
uses: undefined,
}
: mockStep.mockWith;
const updatedStep = { ...oldStep, ...newStep };

for (const key of Object.keys(oldStep)) {
Expand All @@ -72,7 +107,7 @@ export class StepMocker {
jobId: string,
step: StepIdentifier
): { stepIndex: number; step: GithubWorkflowStep | undefined } {
const index = workflow.jobs[jobId]?.steps.findIndex(s => {
const index = workflow.jobs[jobId]?.steps.findIndex((s, index) => {
if (isStepIdentifierUsingId(step)) {
return step.id === s.id;
}
Expand All @@ -88,6 +123,22 @@ export class StepMocker {
if (isStepIdentifierUsingRun(step)) {
return step.run === s.run;
}

if (isStepIdentifierUsingIndex(step)) {
return step.index === index;
}

if (isStepIdentifierUsingBefore(step)) {
return typeof step.before === "string"
? [s.id, s.name, s.uses, s.run].includes(step.before)
: step.before === index;
}

if (isStepIdentifierUsingAfter(step)) {
return typeof step.after === "string"
? [s.id, s.name, s.uses, s.run].includes(step.after)
: step.after === index;
}
return false;
});

Expand Down
35 changes: 33 additions & 2 deletions src/step-mocker/step-mocker.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,18 @@ export type StepIdentifierUsingName = { name: string; mockWith: GithubWorkflowSt
export type StepIdentifierUsingId = { id: string; mockWith: GithubWorkflowStep | string };
export type StepIdentifierUsingUses = { uses: string; mockWith: GithubWorkflowStep | string };
export type StepIdentifierUsingRun = { run: string; mockWith: GithubWorkflowStep | string };
export type StepIdentifierUsingIndex = { index: number; mockWith: GithubWorkflowStep | string };
export type StepIdentifierUsingBefore = { before: number | string; mockWith: GithubWorkflowStep };
export type StepIdentifierUsingAfter = { after: number | string; mockWith: GithubWorkflowStep };

export type StepIdentifier =
| StepIdentifierUsingName
| StepIdentifierUsingId
| StepIdentifierUsingUses
| StepIdentifierUsingRun;
| StepIdentifierUsingRun
| StepIdentifierUsingIndex
| StepIdentifierUsingBefore
| StepIdentifierUsingAfter;

export function isStepIdentifierUsingName(
step: StepIdentifier
Expand All @@ -118,6 +125,30 @@ export function isStepIdentifierUsingUses(

export function isStepIdentifierUsingRun(
step: StepIdentifier
): step is StepIdentifierUsingUses {
): step is StepIdentifierUsingRun {
return Object.prototype.hasOwnProperty.call(step, "run");
}

export function isStepIdentifierUsingIndex(
step: StepIdentifier
): step is StepIdentifierUsingIndex {
return Object.prototype.hasOwnProperty.call(step, "index");
}

export function isStepIdentifierUsingBefore(
step: StepIdentifier
): step is StepIdentifierUsingBefore {
return Object.prototype.hasOwnProperty.call(step, "before");
}

export function isStepIdentifierUsingAfter(
step: StepIdentifier
): step is StepIdentifierUsingAfter {
return Object.prototype.hasOwnProperty.call(step, "after");
}

export function isStepIdentifierUsingBeforeOrAfter(
step: StepIdentifier
): step is StepIdentifierUsingBefore | StepIdentifierUsingAfter {
return isStepIdentifierUsingBefore(step) || isStepIdentifierUsingAfter(step);
}
Loading

0 comments on commit 0b6f849

Please sign in to comment.