Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Follow testing updates #57

Draft
wants to merge 5 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified bun.lockb
Binary file not shown.
17 changes: 8 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
"./dist"
],
"scripts": {
"build": "bun run build:library && bun run build:library:minified",
"build:library": "bun bunchee --runtime=browser --target=es2021",
"build:library:minified": "bun bunchee --runtime=browser --target=es2021 --minify --no-clean --no-dts --output=./dist/index.min.js",
"build": "bun run build:library",
"build:library": "bun bunchee --runtime=browser --target=es2021 --minify --output=./dist/index.mjs",
"fix": "bun run fix:biome && bun run fix:package",
"fix:biome": "bun biome check --write",
"fix:package": "bun sort-package-json --quiet",
Expand All @@ -28,13 +27,13 @@
"test": "bun run lint && bun test"
},
"devDependencies": {
"@biomejs/biome": "~1.9.2",
"@types/bun": "~1.1.10",
"bunchee": "~5.4.0",
"lefthook": "~1.7.16",
"@biomejs/biome": "~1.9.4",
"@types/bun": "~1.1.13",
"bunchee": "~5.6.1",
"deepmerge-ts": "~7.1.3",
"lefthook": "~1.8.2",
"sort-package-json": "~2.10.1",
"ts-deepmerge": "~7.0.1",
"typescript": "~5.6.2"
"typescript": "~5.6.3"
},
"engines": {
"node": ">=18.17.0"
Expand Down
18 changes: 12 additions & 6 deletions src/HTTP.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { merge } from 'ts-deepmerge';
import { deepmerge } from 'deepmerge-ts';
import type { ClientOptions } from './types/JSP.ts';

export class HTTP {
Expand All @@ -9,20 +9,26 @@ export class HTTP {
}

public async fetch<TResponse>(endpoint: string, options: RequestInit): Promise<TResponse> {
const requestOptions = merge(this.options.request, options) as RequestInit;
const requestOptions = deepmerge(this.options.request, options) as RequestInit;

const response = await fetch(this.options.api + endpoint, requestOptions);

return this.parseResponse<TResponse>(response);
}

private parseResponse<TResponse>(response: Response) {
private async parseResponse<TResponse>(response: Response) {
const contentType = response.headers.get('Content-Type');

if (contentType?.startsWith('application/json')) {
return response.json() as Promise<TResponse>;
if (!contentType?.startsWith('application/json')) {
throw new Error('Unknown response type');
}

throw new Error('Unknown response type');
if (!response.ok) {
const error = (await response.json()) as { code: number; type: string; message: string };

throw new Error(`${error.type}: ${error.code}: ${error.message}`);
}

return (await response.json()) as Promise<TResponse>;
}
}
142 changes: 78 additions & 64 deletions src/JSP.spec.ts
Original file line number Diff line number Diff line change
@@ -1,83 +1,97 @@
import { describe, expect, test } from 'bun:test';
import { afterAll, describe, expect, test } from 'bun:test';
import { JSP } from './JSP.ts';

describe('V2', () => {
const jsp = new JSP();

const commonData = {
hello: 'Hello, World!',
bye: 'Bye, World!',
object: {
sample: 'Hello, World!'
},
binary: new Uint8Array([72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33, 10])
};

/**
* An unique key/secret/password for tests.
*/
const commonPrivate: string = Date.now().toString();
const commonPrivateInvalid: string = '_:_:wrongdingdong:_:_';

const testCleanup = (key: string, secret: string) => {
jsp.remove(key, secret);
};

describe('publish', () => {
test('data only', async () => {
const response = await jsp.publish(commonData.hello);
const result = await jsp.access(response.key);

expect(result.data).toBeDefined();
expect(result.data).toBe(commonData.hello);

testCleanup(response.key, response.secret);
const jsp = new JSP();

const commonData = {
hello: 'Hello, World!',
bye: 'Bye, World!',
object: {
sample: 'Hello, World!'
},
binary: new Uint8Array([72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33, 10])
};

/**
* An unique key/secret/password for tests.
*/
const commonPrivate: string = Date.now().toString();
const commonPrivateInvalid: string = '_:_:wrongdingdong:_:_';

const testCleanup = (key: string, secret: string) => {
jsp.remove(key, secret).catch(() => console.error(`Failed to cleanup "${key}" with secret "${secret}"`));
};

describe('publish', async () => {
test('should response parameters be defined and valid', async () => {
// Server should prefer "key" over "keyLength"
const response = await jsp.publish(commonData.hello, {
password: commonPrivate,
key: commonPrivate,
keyLength: 20,
secret: commonPrivate
});

test('key', async () => {
const response = await jsp.publish(commonData.hello, {
key: commonPrivate
});
expect(response.key).toBeDefined();
expect(response.key).toBe(commonPrivate);
expect(response.secret).toBeDefined();
expect(response.secret).toBe(commonPrivate);
expect(response.url).toBeDefined();

expect(response.key).toBeDefined();
expect(response.key).toBe(commonPrivate);
testCleanup(response.key, response.secret);
});

testCleanup(response.key, response.secret);
test('should response "key" length be the same as "keyLength"', async () => {
const keyLength = 20;
const response = await jsp.publish(commonData.hello, {
keyLength
});

test.todo('keyLength', async () => {
const response = await jsp.publish(commonData.hello, {
keyLength: 20
});
expect(response.key).toBeDefined();
expect(response.key.length).toBe(keyLength);
expect(response.secret).toBeDefined();
expect(response.url).toBeDefined();

testCleanup(response.key, response.secret);
});
});

expect(response.key).toBeDefined();
expect(response.key).toHaveLength(20);
describe('access', async () => {
const document = await jsp.publish(commonData.hello, {
secret: commonPrivate
});

testCleanup(response.key, response.secret);
});
const documentProtected = await jsp.publish(commonData.hello, {
password: commonPrivate,
secret: commonPrivate
});

afterAll(() => {
testCleanup(document.key, commonPrivate);
testCleanup(documentProtected.key, commonPrivate);
});

test('password/secret', async () => {
const response = await jsp.publish(commonData.hello, {
password: commonPrivate,
secret: commonPrivate
});
test('should response parameters be defined and valid', async () => {
const response = await jsp.access(document.key);

expect(response.secret).toBe(commonPrivate);
expect(response.key).toBeDefined();
expect(response.key).toBe(document.key);
expect(response.data).toBeDefined();
expect(response.data).toBe(commonData.hello);
expect(response.url).toBeDefined();

const fail = await jsp.access(response.key, {
password: commonPrivateInvalid
});
testCleanup(response.key, commonPrivate);
});

expect(fail.data).toBeUndefined();
test('should fail on protected document', async () => {
const responsePromise = jsp.access(documentProtected.key);

const result = await jsp.access(response.key, {
password: commonPrivate
});
expect(responsePromise).rejects.toThrowError();
});

expect(result.data).toBeDefined();
expect(result.data).toBe(commonData.hello);
test('should fail on bad password protected document', async () => {
const responsePromise = jsp.access(documentProtected.key, { password: commonPrivateInvalid });

testCleanup(response.key, response.secret);
});
expect(responsePromise).rejects.toThrowError();
});
});
30 changes: 14 additions & 16 deletions src/JSP.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
import { merge } from 'ts-deepmerge';
import { deepmerge } from 'deepmerge-ts';
import { version as libraryVersion } from '../package.json';
import { HTTP } from './HTTP.ts';
import { access } from './endpoints/v2/access.ts';
import { edit } from './endpoints/v2/edit.ts';
import { publish } from './endpoints/v2/publish.ts';
import { remove } from './endpoints/v2/remove.ts';
import type { ClientOptions } from './types/JSP.ts';
import type { AccessOptionsV2 } from './types/endpoints/access.ts';
import type { EditOptionsV2 } from './types/endpoints/edit.ts';
import type { PublishOptionsV2 } from './types/endpoints/publish.ts';
import type { AccessOptions } from './types/endpoints/access.ts';
import type { EditOptions } from './types/endpoints/edit.ts';
import type { PublishOptions } from './types/endpoints/publish.ts';

export class JSP {
private static readonly defaultRequestOptions: RequestInit = {
headers: {
'User-Agent': `JSPasteHeadless/${libraryVersion} (https://github.com/jspaste/library)`
}
};

private static readonly defaultOptions: ClientOptions = {
api: 'https://api.inetol.net/jspaste',
request: JSP.defaultRequestOptions
api: 'https://paste.inetol.net/api',
request: {
headers: {
'User-Agent': `JSPasteHeadless/${libraryVersion} (https://github.com/jspaste/library)`
}
}
};

private readonly http: HTTP;

public constructor(clientOptions?: Partial<ClientOptions>) {
const options = clientOptions
? (merge(JSP.defaultOptions, clientOptions) as ClientOptions)
? (deepmerge(JSP.defaultOptions, clientOptions) as ClientOptions)
: JSP.defaultOptions;

this.http = new HTTP(options);
Expand All @@ -35,21 +33,21 @@ export class JSP {
/**
* @version API V2
*/
public async access(key: string, options?: AccessOptionsV2) {
public async access(key: string, options?: AccessOptions) {
return access(this.http, key, options);
}

/**
* @version API V2
*/
public async publish(data: string, options?: PublishOptionsV2) {
public async publish(data: string, options?: PublishOptions) {
return publish(this.http, data, options);
}

/**
* @version API V2
*/
public async edit(data: string, name: string, secret: string, options?: EditOptionsV2) {
public async edit(data: string, name: string, secret: string, options?: EditOptions) {
return edit(this.http, data, name, secret, options);
}

Expand Down
6 changes: 3 additions & 3 deletions src/endpoints/v2/access.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { HTTP } from '../../HTTP.ts';
import type { AccessOptionsV2, AccessResponseV2 } from '../../types/endpoints/access.ts';
import type { AccessOptions, AccessResponse } from '../../types/endpoints/access.ts';

export const access = async (http: HTTP, name: string, options?: AccessOptionsV2) => {
return http.fetch<AccessResponseV2>(`/v2/documents/${name}`, {
export const access = async (http: HTTP, name: string, options?: AccessOptions) => {
return http.fetch<AccessResponse>(`/v2/documents/${name}`, {
method: 'GET',
headers: {
...(options?.password && { password: options.password })
Expand Down
6 changes: 3 additions & 3 deletions src/endpoints/v2/edit.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { HTTP } from '../../HTTP.ts';
import type { EditOptionsV2, EditResponseV2 } from '../../types/endpoints/edit.ts';
import type { EditOptions, EditResponse } from '../../types/endpoints/edit.ts';

export const edit = async (http: HTTP, data: string, name: string, secret: string, options?: EditOptionsV2) => {
return http.fetch<EditResponseV2>(`/v2/documents/${name}`, {
export const edit = async (http: HTTP, data: string, name: string, secret: string, options?: EditOptions) => {
return http.fetch<EditResponse>(`/v2/documents/${name}`, {
method: 'PATCH',
body: data,
headers: {
Expand Down
6 changes: 3 additions & 3 deletions src/endpoints/v2/publish.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { HTTP } from '../../HTTP.ts';
import type { PublishOptionsV2, PublishResponseV2 } from '../../types/endpoints/publish.ts';
import type { PublishOptions, PublishResponse } from '../../types/endpoints/publish.ts';

export const publish = async (http: HTTP, data: string, options?: PublishOptionsV2) => {
return http.fetch<PublishResponseV2>('/v2/documents', {
export const publish = async (http: HTTP, data: string, options?: PublishOptions) => {
return http.fetch<PublishResponse>('/v2/documents', {
method: 'POST',
body: data,
headers: {
Expand Down
4 changes: 2 additions & 2 deletions src/endpoints/v2/remove.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { HTTP } from '../../HTTP.ts';
import type { RemoveResponseV2 } from '../../types/endpoints/remove.ts';
import type { RemoveResponse } from '../../types/endpoints/remove.ts';

export const remove = async (http: HTTP, name: string, secret: string) => {
return http.fetch<RemoveResponseV2>(`/v2/documents/${name}`, {
return http.fetch<RemoveResponse>(`/v2/documents/${name}`, {
method: 'DELETE',
headers: {
secret: secret
Expand Down
7 changes: 3 additions & 4 deletions src/types/endpoints/access.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
type AccessOptionsV2 = {
type AccessOptions = {
password?: string;
};

type AccessResponseV2 = {
type AccessResponse = {
key: string;
data: string;
url: string;
expirationTimestamp: number;
};

export type { AccessOptionsV2, AccessResponseV2 };
export type { AccessOptions, AccessResponse };
6 changes: 3 additions & 3 deletions src/types/endpoints/edit.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
type EditOptionsV2 = {
type EditOptions = {
password?: string;
};

type EditResponseV2 = {
type EditResponse = {
edited: boolean;
};

export type { EditOptionsV2, EditResponseV2 };
export type { EditOptions, EditResponse };
7 changes: 3 additions & 4 deletions src/types/endpoints/publish.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
type PublishOptionsV2 = {
type PublishOptions = {
password?: string;
key?: string;
keyLength?: number;
secret?: string;
};

type PublishResponseV2 = {
type PublishResponse = {
key: string;
secret: string;
url: string;
expirationTimestamp: number;
};

export type { PublishOptionsV2, PublishResponseV2 };
export type { PublishOptions, PublishResponse };
Loading