Skip to content

Commit

Permalink
Merge pull request #4 from gemini-testing/base.implementation
Browse files Browse the repository at this point in the history
feat: plugin base implementation
  • Loading branch information
eGavr authored Jun 29, 2022
2 parents 6292d84 + 8ca787d commit faf83f4
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 5 deletions.
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
# hermione-oauth

Plugin for convenient setting of authorization header with OAuth token
Some Remote WebDriver Servers requires OAuth authorization for each request. This plugin is useful for setting of authorization header with OAuth token.

## Install

```bash
npm install hermione-oauth --save-dev
```

## Usage

```js
// .hermione.conf.js
module.exports = {
// ...
plugins: {
"hermione-oauth": {
enabled: true, // plugin is enabled by default
token: "<token>", // option also accepts absolute filepath with a token
help: "https://...", // information on where to get a token
},
},
};
```

Each plugin option can be redefined by
- environment variable which starts with prefix `hermione_oauth_`, for example, `hermione_oauth_token=123-456-789`
- CLI option which starts with prefix `--oauth-`, for example, `--oauth-token=123-456-789`
56 changes: 56 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { option, root, section } from "gemini-configparser";
import type { Parser } from "gemini-configparser";

import { PluginOptionTypeError, PluginTokenOptionAbsenceError } from "./errors";

export type PluginConfig = {
enabled: boolean;
token: string;
help: string;
};

const isNonEmptyString = (v: unknown): boolean => typeof v === "string" && v !== "";

const assertType = <T>(name: string, validate: (v: unknown) => boolean, type: string) => {
return (v: T) => {
if (!validate(v)) {
throw new PluginOptionTypeError(name, type);
}
};
};

const boolean = (name: string): Parser<boolean> =>
option({
parseEnv: v => Boolean(JSON.parse(v)),
parseCli: v => Boolean(JSON.parse(v)),
defaultValue: true,
validate: assertType(name, v => typeof v === "boolean", "boolean"),
});

const nonEmptyString = (name: string): Parser<string> =>
option({
defaultValue: "",
validate: assertType(name, isNonEmptyString, "non empty string"),
});

export function parseConfig(options: Record<string, unknown>): PluginConfig {
const { env, argv } = process;

const parseOptions = root<PluginConfig>(
section({
enabled: boolean("enabled"),
token: option({
defaultValue: "",
validate: (v, config) => {
if (!isNonEmptyString(v)) {
throw new PluginTokenOptionAbsenceError(config.help);
}
},
}),
help: nonEmptyString("help"),
}),
{ envPrefix: "hermione_oauth_", cliPrefix: "--oauth-" },
);

return parseOptions({ options, env, argv });
}
35 changes: 35 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class PluginError extends Error {
constructor(message: string) {
super(`hermione-oauth: ${message}`);
}
}

export class PluginOptionTypeError extends PluginError {
constructor(name: string, type: string) {
super(`'${name}' option must be of a ${type} type`);
}
}

export class PluginTokenError extends PluginError {
constructor(message: string, help: string) {
super(`${message}, see ${help} to get it`);
}
}

export class PluginTokenReadError extends PluginTokenError {
constructor(path: string, help: string) {
super(`unable to read token from file ${path}`, help);
}
}

export class PluginTokenOptionAbsenceError extends PluginTokenError {
constructor(help: string) {
super("'token' option must be of a non empty string type", help);
}
}

export class PluginTokenAbsenceError extends PluginTokenError {
constructor(filepath: string, help: string) {
super(`token is absence at file ${filepath}`, help);
}
}
8 changes: 4 additions & 4 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe("hermione-oauth", () => {
const hermioneMock = (browsers: Record<string, Hermione.BrowserConfig>): Hermione => {
const emitter = new EventEmitter() as unknown as Hermione;

emitter.events = { INIT: "init" } as Hermione.EVENTS;
emitter.events = { BEGIN: "begin" } as Hermione.EVENTS;
emitter.config = {
forBrowser: (id: string) => browsers[id],
getBrowserIds: () => Object.keys(browsers),
Expand All @@ -30,7 +30,7 @@ describe("hermione-oauth", () => {
const hermione = hermioneMock({ "<bro-id>": browser(config) });

plugin(hermione, { enabled: false });
hermione.emit(hermione.events.INIT);
hermione.emit(hermione.events.BEGIN);

expect(config).toEqual({ headers: { "<foo>": "<bar>" } });
});
Expand All @@ -40,7 +40,7 @@ describe("hermione-oauth", () => {
const hermione = hermioneMock({ "<bro1-id>": browser(config1), "<bro2-id>": browser(config2) });

plugin(hermione, { enabled: true, token: "123456789" });
hermione.emit(hermione.events.INIT);
hermione.emit(hermione.events.BEGIN);

expect(config1).toEqual({ headers: { "<foo>": "<bar>", Authorization: "OAuth 123456789" } });
expect(config2).toEqual({ headers: { "<baz>": "<quux>", Authorization: "OAuth 123456789" } });
Expand All @@ -62,7 +62,7 @@ describe("hermione-oauth", () => {
const hermione = hermioneMock({ "<bro1-id>": browser(config1), "<bro2-id>": browser(config2) });

plugin(hermione, { enabled: true, token: "/foo/bar" });
hermione.emit(hermione.events.INIT);
hermione.emit(hermione.events.BEGIN);

expect(config1).toEqual({ headers: { "<foo>": "<bar>", Authorization: "OAuth 987654321" } });
expect(config2).toEqual({ headers: { "<baz>": "<quux>", Authorization: "OAuth 987654321" } });
Expand Down
24 changes: 24 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import path from "path";

import type Hermione from "hermione";

import { parseConfig } from "./config";
import readToken from "./read-token";

export = (hermione: Hermione, options: Record<string, unknown>): void => {
const config = parseConfig(options);

if (!config.enabled) {
return;
}

const token = path.isAbsolute(config.token) ? readToken(config.token, config.help) : config.token;

hermione.on(hermione.events.BEGIN, () => {
hermione.config.getBrowserIds().forEach(browserId => {
const browserConfig = hermione.config.forBrowser(browserId);

browserConfig.headers = { ...browserConfig.headers, Authorization: `OAuth ${token}` };
});
});
};
19 changes: 19 additions & 0 deletions src/read-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import fs from "fs";

import { PluginTokenReadError, PluginTokenAbsenceError } from "./errors";

export default function (filepath: string, help: string): string {
let token: string;

try {
token = fs.readFileSync(filepath, { encoding: "utf-8" }).trim();
} catch (e) {
throw new PluginTokenReadError(filepath, help);
}

if (token === "") {
throw new PluginTokenAbsenceError(filepath, help);
}

return token;
}

0 comments on commit faf83f4

Please sign in to comment.