Skip to content

Commit

Permalink
Merge pull request #402 from wuespace/TELESTION-451
Browse files Browse the repository at this point in the history
TELESTION-451
  • Loading branch information
pklaschka authored Dec 19, 2023
2 parents 9a0e831 + 6d31da6 commit 30bf35e
Show file tree
Hide file tree
Showing 32 changed files with 971 additions and 40 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/backend-deno-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Backend Deno CI

on: [ push, pull_request ]

defaults:
run:
working-directory: ./backend-deno

jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout 📥
uses: actions/checkout@v2
- name: Run tests 🛃
run: docker compose up --abort-on-container-exit
- name: Stop containers 🛑
if: always()
run: docker compose down
17 changes: 17 additions & 0 deletions .github/workflows/backend-features-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Backend Features Lint

on: [ push, pull_request ]

defaults:
run:
working-directory: ./backend-features

jobs:
lint:
name: Gherkin Lint
runs-on: ubuntu-latest
steps:
- name: Checkout 📥
uses: actions/checkout@v2
- name: Run Linter 📑
run: docker compose run lint
25 changes: 7 additions & 18 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,11 @@ jobs:
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 21
- name: Install pnpm
run: npm install -g pnpm
- name: Build API Reference for frontend-react
working-directory: frontend-react
run: |
pnpm install --frozen-lockfile
pnpm run docs
- name: Build docs
working-directory: docs
run: docker compose run mkdocs build
- name: Deploy docs
uses: mhausenblas/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CUSTOM_DOMAIN: docs.telestion.wuespace.de
CONFIG_FILE: docs/mkdocs.yml
EXTRA_PACKAGES: build-base
REQUIREMENTS: docs/requirements.txt
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/site
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.DS_Store
.vscode
.idea
6 changes: 6 additions & 0 deletions backend-deno/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"recommendations": [
"cucumberopen.cucumber-official",
"denoland.vscode-deno"
]
}
5 changes: 5 additions & 0 deletions backend-deno/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"deno.enable": true,
"deno.lint": true,
"deno.unstable": true,
}
47 changes: 47 additions & 0 deletions backend-deno/cucumber/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Cucumber Testing Implementation

Since we are using Deno, there is no official Cucumber implementation for Deno.
However, it's pretty easy to parse the Gherkin files and execute the steps.
This folder contains a custom implementation of Cucumber for Deno.

## How to run the tests

Run the following command to run the tests:

```bash
docker compose up
```

In the background, this uses `deno test` with the [`test.ts`](test.ts) file as entrypoint.

## Feature file location

Since the feature files specify the general behavior of services, they are independent of the implementation (here:
Deno).
Therefore, the feature files are located in the repo's [`/backend-features`](../../backend-features) folder.

## Setting up VSCode

1. Open both folders (`/backend-deno` and `/backend-features`) in VSCode in the same workspace.
2. Install recommended extensions.
3. Open the workspace settings and add the following:

```json
{
"cucumberautocomplete.steps": ["backend-deno/**/*.ts"],
"cucumberautocomplete.syncfeatures": "backend-features/**/*.feature"
}
```

Now, you should have autocompletion for the step definitions and the feature files.

## Unsupported Gherkin features

As of right now, the following Gherkin features are not supported:

- Doc Strings
- Data Tables
- Backgrounds
- Tags
- Scenario Outlines
- Examples
3 changes: 3 additions & 0 deletions backend-deno/cucumber/deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "https://deno.land/[email protected]/assert/mod.ts";
export * from "https://deno.land/[email protected]/path/mod.ts";
export { parseArgs } from "https://deno.land/[email protected]/cli/parse_args.ts";
149 changes: 149 additions & 0 deletions backend-deno/cucumber/step-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { resolve } from "./deps.ts";

/**
* A step definition
*
* @see https://cucumber.io/docs/cucumber/step-definitions/?lang=javascript
*/
interface StepDefinition {
/**
* The name of the step definition that gets matched against the step name
*/
name: string;
/**
* Check if the step matches the step definition
* @param step The step name
* @returns `false` if the step does not match, otherwise an array of parameters
*/
matches: (step: string) => string[] | false;
/**
* The action to perform when the step is executed
* @param ctx A context object that can be used to share state between steps
* @param params Parameters extracted from the step name
* @returns potentially a promise that resolves when the step is done
*/
action: (
ctx: Record<string, unknown>,
...params: string[]
) => void | Promise<void>;
}

/**
* A registry of parameters that can be used in step definition names
*/
const paramRegistry: Record<string, {
regex: RegExp;
}> = {
"{string}": {
regex: /^"([^"]+)"$/,
},
};

/**
* A registry of step definitions
*/
const stepRegistry: StepDefinition[] = [];

/**
* Register a step definition to be used in scenarios
* @param name the name of the step definition
* @param action the action to perform when the step is executed
*/
function registerStep(name: string, action: StepDefinition["action"]) {
stepRegistry.push({
name,
action,
matches: (step: string) => {
let regex = "^" + escapeRegExp(name) + "$";
for (const param in paramRegistry) {
let paramRegex = paramRegistry[param].regex.source;
if (paramRegex.startsWith("^")) {
paramRegex = paramRegex.slice(1);
}
if (paramRegex.endsWith("$")) {
paramRegex = paramRegex.slice(0, -1);
}
regex = regex.replaceAll(escapeRegExp(param), paramRegex);
}

const match = step.match(new RegExp(regex));

if (match) {
return match.slice(1);
}

return false;
},
});
}

/**
* Escape special characters in a string to be used in a regular expression
* @param string input
* @returns `input` with all special characters escaped
*
* @see https://stackoverflow.com/a/6969486/9276604 by user coolaj86 (CC BY-SA 4.0)
*/
function escapeRegExp(string: string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}

/**
* Import all step definitions from a folder
* @param stepsFolderPath the path to the folder containing the step definitions
*/
export async function importStepDefinitions(stepsFolderPath: string) {
const files = Deno.readDirSync(stepsFolderPath);

for (const file of files) {
const filePath = resolve(stepsFolderPath, file.name);

if (file.isDirectory || !file.name.endsWith(".ts")) {
continue;
}

console.debug(`Importing step file: ${filePath}`);
await import(filePath);
}

console.debug("Steps imported");
}

/**
* Retrieve the action to perform when a step is executed
* @param name the name of the step
* @returns the `StepDefinition.action` function if a step definition matches the step name, otherwise `undefined`
*/
export function getStep(name: string): StepDefinition["action"] | undefined {
const step = stepRegistry.find((step) => step.matches(name));
return step
? (ctx) => step.action(ctx, ...step.matches(name) as string[])
: undefined;
}

/**
* Register a step definition to be used in scenarios
* @param name the name of the step definition. Can contain parameters.
* @param action the action to perform when the step is executed
*/
export function Given(name: string, action: StepDefinition["action"]) {
registerStep(name, action);
}

/**
* Register a step definition to be used in scenarios
* @param name the name of the step definition. Can contain parameters.
* @param action the action to perform when the step is executed
*/
export function When(name: string, action: StepDefinition["action"]) {
registerStep(name, action);
}

/**
* Register a step definition to be used in scenarios
* @param name the name of the step definition. Can contain parameters.
* @param action the action to perform when the step is executed
*/
export function Then(name: string, action: StepDefinition["action"]) {
registerStep(name, action);
}
30 changes: 30 additions & 0 deletions backend-deno/cucumber/steps/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { startService } from "../../mod.ts";
import { Given, Then } from "../step-registry.ts";
import { assertEquals } from "./deps.ts";

Given('I have an environment variable named {string} with value {string}', (_ctx, key, value) => {
Deno.env.set(key, value);
});

Given('I have the basic service configuration', () => {
Object.keys(Deno.env.toObject()).forEach((key) => {
Deno.env.delete(key);
});
Deno.env.set('NATS_URL', 'localhost:4222');
Deno.env.set('DATA_DIR', '/tmp/deno-gherkin');
Deno.env.set('SERVICE_NAME', 'deno-gherkin');
});

Then('the service should be configured with {string} set to {string}', (ctx, key, shouldBe) => {
const theService = ctx.service as Awaited<ReturnType<typeof startService>>;

const value = theService.config[key];

assertEquals((value ?? 'undefined').toString(), shouldBe);
});

Given('I have no service configuration', () => {
Object.keys(Deno.env.toObject()).forEach((key) => {
Deno.env.delete(key);
});
})
5 changes: 5 additions & 0 deletions backend-deno/cucumber/steps/deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from "https://deno.land/[email protected]/assert/mod.ts"
export type {
ConnectionOptions,
NatsConnection,
} from "https://deno.land/x/[email protected]/nats-base-client/mod.ts";
Loading

0 comments on commit 30bf35e

Please sign in to comment.