Skip to content

Commit

Permalink
feat: implement testing using node:assert & node:test
Browse files Browse the repository at this point in the history
  • Loading branch information
alii committed Sep 9, 2022
1 parent ca6ca7e commit e154fd6
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 34 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ node_modules
dist
_workbench.ts
docs
.env
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@
"build": "rm -rf dist && tsup",
"bench": "HOP_DEBUG=true tsx _workbench.ts",
"release": "yarn build && yarn npm publish",
"vercel-build": "yarn typedoc --plugin typedoc-plugin-missing-exports src/index.ts"
"vercel-build": "yarn typedoc --plugin typedoc-plugin-missing-exports src/index.ts",
"test": "tsx tests/index.ts"
},
"devDependencies": {
"@types/glob": "^8.0.0",
"@types/node": "^18.7.14",
"dotenv": "^16.0.2",
"glob": "^8.0.3",
"prettier": "^2.7.1",
"tsup": "^6.2.3",
Expand Down
4 changes: 2 additions & 2 deletions src/rest/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import {getIdPrefix, Id, Method} from './types/index.js';

export type APIAuthentication = Id<'ptk'> | Id<'bearer'> | Id<'pat'>;

export type APIAuthenticationType = APIAuthentication extends Id<infer T>
export type APIAuthenticationPrefix = APIAuthentication extends Id<infer T>
? T
: never;

export function validateAPIAuthentication(
auth: string,
): auth is APIAuthenticationType {
): auth is APIAuthenticationPrefix {
return auth === 'bearer' || auth === 'pat' || auth === 'ptk';
}

Expand Down
2 changes: 2 additions & 0 deletions src/util/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ export const IS_NODE =
process.versions.node != null &&
typeof Bun === 'undefined' &&
typeof Deno === 'undefined';

export const SUPPORTS_INTL = typeof Intl !== 'undefined';
9 changes: 9 additions & 0 deletions src/util/lists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {SUPPORTS_INTL} from './constants.js';

export function formatList(list: string[], type: Intl.ListFormatType): string {
if (SUPPORTS_INTL) {
return new Intl.ListFormat('en-US', {type}).format(list);
}

return list.join(', ');
}
69 changes: 38 additions & 31 deletions src/util/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {formatList} from './lists.js';

export type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';

export type Empty = void;
Expand Down Expand Up @@ -129,13 +131,17 @@ export function validateIdPrefix<T extends IdPrefixes = IdPrefixes>(
*/
export function validateId<T extends IdPrefixes = IdPrefixes>(
maybeId: string,
prefix?: T,
prefix?: T | T[],
): maybeId is Id<T> {
if (Array.isArray(prefix)) {
return prefix.some(p => validateId(maybeId, p));
}

if (!prefix) {
return ID_PREFIXES.some(({prefix}) => maybeId.startsWith(`${prefix}_`));
}

return maybeId.startsWith(prefix);
return maybeId.startsWith(`${prefix}_`);
}

export function getIdPrefix<T extends IdPrefixes>(id: string, expect?: T) {
Expand All @@ -156,42 +162,43 @@ export function getIdPrefix<T extends IdPrefixes>(id: string, expect?: T) {
return prefix;
}

/**
* Casts a variable into an ID for TypeScript
*
* @param id The variable to cast into an id
* @param prefix The type of id to cast to
* @returns A valid id string
*
* @example
* ```ts
* declare function createContainer(id: Id<'container'>): void
* declare const containerId: string;
*
* // Error, string cannot be assigned to Id<'container'>
* createContainer(containerId);
*
* // Successfully casts and compiles
* createContainer(asId(containerId, 'container'));
* ```
*/
export function asId<T extends IdPrefixes>(id: string, prefix: T) {
return id as Id<T>;
export function id<T extends IdPrefixes = IdPrefixes>(
maybeId?: string,
prefix?: T | T[],
) {
assertId(maybeId, prefix);
return maybeId;
}

/**
* Alias for {@link asId}
*/
export const id = asId;

export function assertId<T extends IdPrefixes = IdPrefixes>(
maybeId: string,
prefix?: T,
maybeId?: string,
prefix?: T | T[],
message?: string,
): asserts maybeId is Id<T> {
const expectedPrefix =
prefix === undefined
? '<prefix>'
: Array.isArray(prefix)
? formatList(prefix, 'disjunction')
: prefix;

if (!maybeId) {
throw new Error(
message ??
`No value specified trying to assert an ID. Expected \`${expectedPrefix}\` and found ${maybeId}.`,
);
}

if (!validateId(maybeId, prefix)) {
const expectedPrefix =
prefix === undefined
? undefined
: Array.isArray(prefix)
? formatList(prefix, 'disjunction')
: prefix;

throw new Error(
message ?? `Invalid id: ${maybeId}. Expected ${prefix}_{string}`,
message ?? `Invalid id: ${maybeId}. Expected \`${expectedPrefix}\`.`,
);
}
}
78 changes: 78 additions & 0 deletions tests/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import 'dotenv/config';

import assert from 'node:assert/strict';
import {test} from 'node:test';

import {id, Hop, RuntimeType, validateId} from '../src/index.js';

const hop = new Hop(
id(process.env.HOP_TOKEN, ['ptk', 'bearer', 'pat']),
'https://api-staging.hop.io',
);

test('It validates that the token is valid', () => {
assert(validateId('ptk_testing', 'ptk'), "Couldn't validate Project Token");
});

test('it creates a deployment', async t => {
const redis = await hop.ignite.deployments.create({
version: '2022-05-17',
name: 'redis',
image: {
name: 'redis',
auth: null,
gh_repo: null,
},
container_strategy: 'manual',
type: RuntimeType.PERSISTENT,
env: {},
resources: {
vcpu: 0.5,
ram: '128mb',
vgpu: [],
},
});

assert.ok(
validateId(redis.id, 'deployment'),
"Couldn't validate deployment ID",
);

assert.equal(redis.name, 'redis');
assert.equal(typeof redis.created_at, 'string');
assert.doesNotThrow(() => new Date(redis.created_at));

t.todo('See if we can check the functions that exist on a deployment');

assert.deepStrictEqual(redis, {
config: {
container_strategy: 'manual',
env: {},
image: {
auth: null,
name: 'redis:latest',
},
resources: {
ram: '128mb',
vcpu: 0.5,
},
restart_policy: 'on-failure',
type: 'persistent',
version: '2022-05-17',
},
name: 'redis',
container_count: 0,

// These values are dynamic and will change
// so we can't really test them with .deepStrictEqual
created_at: redis.created_at,
id: redis.id,
createContainer: redis.createContainer,
createGateway: redis.createGateway,
delete: redis.delete,
getContainers: redis.getContainers,
});

// Cleanup
await redis.delete();
});
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ __metadata:
"@types/glob": ^8.0.0
"@types/node": ^18.7.14
cross-fetch: ^3.1.5
dotenv: ^16.0.2
glob: ^8.0.3
prettier: ^2.7.1
tsup: ^6.2.3
Expand Down Expand Up @@ -464,6 +465,13 @@ __metadata:
languageName: node
linkType: hard

"dotenv@npm:^16.0.2":
version: 16.0.2
resolution: "dotenv@npm:16.0.2"
checksum: ca8f9ca2d67929c7771069f4c31b4e46b9932621009e658e5afd655dde2d69b77642bf36dbc9e72bc170523dfd908a9ee41c26f034c1fdc605ace3b1b4b10faf
languageName: node
linkType: hard

"emoji-regex@npm:^8.0.0":
version: 8.0.0
resolution: "emoji-regex@npm:8.0.0"
Expand Down

0 comments on commit e154fd6

Please sign in to comment.