Skip to content

Commit

Permalink
feat: added cookie setter and unified auth method
Browse files Browse the repository at this point in the history
still wip
  • Loading branch information
eddienubes committed Feb 10, 2024
1 parent 373d5c5 commit c84ea5d
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 21 deletions.
82 changes: 65 additions & 17 deletions src/Sage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
statusCodeToMessage,
parseJsonStr,
isRedirect,
isError
isError,
wrapArray
} from './utils.js';
import { SageHttpResponse } from './SageHttpResponse.js';
import path from 'node:path';
Expand Down Expand Up @@ -69,6 +70,10 @@ export class Sage {
this.client = new Client(`http://localhost:${port}`, httpClientOptions);
}

/**
* Sets query parameters for the request.
* @param query
*/
query(query: Record<string | number, string>): this {
this.request.query = query;
return this;
Expand Down Expand Up @@ -104,28 +109,54 @@ export class Sage {
* @param key
* @param value
*/
set(key: string, value: string): this {
this.request.headers = { ...(this.request.headers || {}), [key]: value };
set(key: string, value: string | string[]): this {
if (!this.request.headers) {
this.request.headers = {};
}

value = wrapArray(value);

// If already an array
if (Array.isArray(this.request.headers[key])) {
const existingValue = this.request.headers[key] as string[];
existingValue.push(...value);
return this;
}

// If an existing value is a string, convert it to an array
if (typeof this.request.headers[key] === 'string') {
const existingValue = this.request.headers[key] as string;
this.request.headers[key] = [existingValue, ...value];
return this;
}

// If a single value is passed, don't wrap it in an array
if (value.length === 1) {
this.request.headers[key] = value[0];
return this;
}

this.request.headers[key] = value;
return this;
}

/**
* Sets the Authorization header to base64 of Bearer token with a Basic Prefix.
* @param username
* If password is provided, it will be used to create a Basic Auth header.
* If password is not provided, it will be used as a Bearer token.
* Automatically adds Basic or Bearer prefix to the token.
* @param usernameOrToken
* @param password
*/
basic(username: string, password: string): this {
const encoded = Buffer.from(`${username}:${password}`).toString('base64');
this.set('Authorization', `Basic ${encoded}`);
return this;
}
auth(usernameOrToken: string, password?: string): this {
if (password) {
const credentials = Buffer.from(
`${usernameOrToken}:${password}`
).toString('base64');
this.set('Authorization', `Basic ${credentials}`);
return this;
}

/**
* Sets the Authorization header to Bearer token. The prefix will be added.
* @param token
*/
bearer(token: string): this {
this.set('Authorization', `Bearer ${token}`);
this.set('Authorization', `Bearer ${usernameOrToken}`);
return this;
}

Expand Down Expand Up @@ -254,7 +285,24 @@ export class Sage {
} satisfies SageHttpResponse);
} catch (e) {
throw new SageException(
`Failed to make a request to the underlying server, please take a look at the upstream error for more details: `,
`
Failed;
to;
make;
a;
request;
to;
the;
underlying;
server, please;
take;
a;
look;
at;
the;
upstream;
error;
for more details: `,
e
);
} finally {
Expand Down
8 changes: 8 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,11 @@ export const isObject = (candidate: unknown): candidate is object => {
export const copyObject = <T>(obj: T): T => {
return JSON.parse(JSON.stringify(obj));
};

export const wrapArray = <T>(value: T | T[]): T[] => {
if (Array.isArray(value)) {
return value;
}

return [value];
};
14 changes: 10 additions & 4 deletions test/Sage.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { createServer } from 'node:http';
import { Sage } from '../src/Sage.js';
import { getExpressApp, getFastifyApp } from './utils.js';
import { ConfigStore } from '../src/ConfigStore.js';
import { SAGE_DEFAULT_CONFIG } from '../src/index.js';

describe('Sage', () => {
it('should initialize sage assistant', async () => {
const expressServer = createServer(getExpressApp());
const fastifyServer = getFastifyApp();
const server = createServer();
const store = new ConfigStore(SAGE_DEFAULT_CONFIG);
const config = store.getConfig();

const sage1 = new Sage(
{
Expand All @@ -18,7 +22,8 @@ describe('Sage', () => {
launched: false
},
'GET',
'/test'
'/test',
config
);
const sage2 = new Sage(
{
Expand All @@ -30,7 +35,8 @@ describe('Sage', () => {
launched: false
},
'GET',
'/test'
'/test',
config
);
const sage3 = new Sage(
{
Expand All @@ -40,9 +46,9 @@ describe('Sage', () => {
launched: false
},
'GET',
'/test'
'/test',
config
);

expect(sage1).toBeTruthy();
expect(sage2).toBeTruthy();
expect(sage3).toBeTruthy();
Expand Down
143 changes: 143 additions & 0 deletions test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,70 @@ describe('request', () => {
});
});
});
describe('header', () => {
it('should set and pass a single header', async () => {
const res = await request(expressApp)
.post('/ping-pong')
.set('x-custom-header', 'custom-value');

expect(res).toMatchObject({
body: {
reqHeaders: {
'x-custom-header': 'custom-value'
}
}
} as SageHttpResponse);
});

it('should set and pass 2 values in a single header', async () => {
const res = await request(expressApp)
.post('/ping-pong')
.set('x-custom-header', 'custom-value1')
.set('x-custom-header', 'custom-value2');

expect(res).toMatchObject({
body: {
reqHeaders: {
'x-custom-header': 'custom-value1, custom-value2'
}
}
} as SageHttpResponse);
});

it('should set and pass 3 values in a single header', async () => {
const res = await request(expressApp)
.post('/ping-pong')
.set('x-custom-header', 'custom-value1')
.set('x-custom-header', 'custom-value2')
.set('x-custom-header', 'custom-value3');

expect(res).toMatchObject({
body: {
reqHeaders: {
'x-custom-header': 'custom-value1, custom-value2, custom-value3'
}
}
} as SageHttpResponse);
});

it('should accept arrays', async () => {
const res = await request(expressApp)
.post('/ping-pong')
.set('x-custom-header', [
'custom-value1',
'custom-value2',
'custom-value3'
]);

expect(res).toMatchObject({
body: {
reqHeaders: {
'x-custom-header': 'custom-value1, custom-value2, custom-value3'
}
}
} as SageHttpResponse);
});
});
});

describe('fastify', () => {
Expand Down Expand Up @@ -604,5 +668,84 @@ describe('request', () => {
} as SageHttpResponse);
});
});

describe('header', () => {
it('should set and pass a single header', async () => {
const res = await request(fastifyApp.server)
.post('/ping-pong')
.set('x-custom-header', 'custom-value');

expect(res).toMatchObject({
body: {
reqHeaders: {
'x-custom-header': 'custom-value'
}
}
} as SageHttpResponse);
});

it('should set and pass 2 values in a single header', async () => {
const res = await request(fastifyApp.server)
.post('/ping-pong')
.set('x-custom-header', 'custom-value1')
.set('x-custom-header', 'custom-value2');

expect(res).toMatchObject({
body: {
reqHeaders: {
'x-custom-header': 'custom-value1, custom-value2'
}
}
} as SageHttpResponse);
});

it('should set and pass 3 values in a single header', async () => {
const res = await request(fastifyApp.server)
.post('/ping-pong')
.set('x-custom-header', 'custom-value1')
.set('x-custom-header', 'custom-value2')
.set('x-custom-header', 'custom-value3');

expect(res).toMatchObject({
body: {
reqHeaders: {
'x-custom-header': 'custom-value1, custom-value2, custom-value3'
}
}
} as SageHttpResponse);
});

it('should accept array with multiple values', async () => {
const res = await request(fastifyApp.server)
.post('/ping-pong')
.set('x-custom-header', [
'custom-value1',
'custom-value2',
'custom-value3'
]);

expect(res).toMatchObject({
body: {
reqHeaders: {
'x-custom-header': 'custom-value1, custom-value2, custom-value3'
}
}
} as SageHttpResponse);
});

it('should accept arrays with a single value', async () => {
const res = await request(fastifyApp.server)
.post('/ping-pong')
.set('x-custom-header', ['custom-value1']);

expect(res).toMatchObject({
body: {
reqHeaders: {
'x-custom-header': 'custom-value1'
}
}
} as SageHttpResponse);
});
});
});
});

0 comments on commit c84ea5d

Please sign in to comment.