Skip to content

Commit

Permalink
Adds support for discovering with headers
Browse files Browse the repository at this point in the history
  • Loading branch information
JDurstberger committed Dec 6, 2023
1 parent 355bc75 commit dc641c0
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 105 deletions.
8 changes: 6 additions & 2 deletions src/hal-http-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ export type Response = {
resource: Resource
}

const get = async (url: string): Promise<Response> => {
export type Options = {
headers?: { [key: string]: string }
}

const get = async (url: string, options?: Options): Promise<Response> => {
try {
const response = await axios.get(url)
const response = await axios.get(url, options)
return {
status: response.status,
resource: Resource.fromObject(response.data),
Expand Down
8 changes: 6 additions & 2 deletions src/navigator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Resource } from './resource'
import { halHttpClient } from './hal-http-client'

export type Options = {
headers?: { [key: string]: string }
}

export class Navigator {
readonly status: number
readonly resource: Resource
Expand All @@ -9,8 +13,8 @@ export class Navigator {
this.resource = resource
}

static async discover(url: string): Promise<Navigator> {
const response = await halHttpClient.get(url)
static async discover(url: string, options?: Options): Promise<Navigator> {
const response = await halHttpClient.get(url, options)
return new Navigator(response.status, response.resource)
}

Expand Down
219 changes: 118 additions & 101 deletions test/navigator.test.ts
Original file line number Diff line number Diff line change
@@ -1,135 +1,152 @@
import { Navigator } from '../src'
import { setupServer } from 'msw/node'
import { http, HttpResponse } from 'msw'
import { faker } from '@faker-js/faker'
import { randomProperty } from './data'
import { MockServer } from './server'

const server = setupServer()
const server = new MockServer()

beforeAll(() => server.listen())
afterAll(() => server.close())
beforeEach(() => server.resetHandlers())

describe('navigator', () => {
test('status is 200 when discovering endpoint succeeds', async () => {
const url = 'https://example.com'
server.use(http.get(url, () => HttpResponse.json({})))
describe('discover', () => {
test('passes headers when discovering endpoint', async () => {
const url = 'https://example.com'
const headerKey = faker.lorem.word()
const headerValue = faker.lorem.word()
const headers = {[headerKey]: headerValue}
server.use(http.get(url, () => HttpResponse.json({})))

await Navigator.discover(url, {headers})

expect(server.recordedRequests).toHaveLength(1)
expect(server.recordedRequests[0].headers.get(headerKey)).toStrictEqual(headerValue)
})

const navigator = await Navigator.discover(url)
test('status is 200 when discovering endpoint succeeds', async () => {
const url = 'https://example.com'
server.use(http.get(url, () => HttpResponse.json({})))

expect(navigator.status).toBe(200)
})
const navigator = await Navigator.discover(url)

test('resource is empty resource when discovering endpoint returns empty json object', async () => {
const url = 'https://example.com'
const resource = {}
server.use(http.get(url, () => HttpResponse.json(resource)))
expect(navigator.status).toBe(200)
})

const navigator = await Navigator.discover(url)
test('resource is empty resource when discovering endpoint returns empty json object', async () => {
const url = 'https://example.com'
const resource = {}
server.use(http.get(url, () => HttpResponse.json(resource)))

expect(navigator.status).toBe(200)
expect(navigator.resource.toObject()).toStrictEqual(resource)
})
const navigator = await Navigator.discover(url)

test('resource has property when discovering endpoint succeeds with json with property', async () => {
const key = faker.lorem.word()
const value = randomProperty()
const url = 'https://example.com'
const resource = { [key]: value }
server.use(http.get(url, () => HttpResponse.json(resource)))
expect(navigator.status).toBe(200)
expect(navigator.resource.toObject()).toStrictEqual(resource)
})

const navigator = await Navigator.discover(url)
test('resource has property when discovering endpoint succeeds with json with property', async () => {
const key = faker.lorem.word()
const value = randomProperty()
const url = 'https://example.com'
const resource = { [key]: value }
server.use(http.get(url, () => HttpResponse.json(resource)))

expect(navigator.status).toBe(200)
expect(navigator.resource.getProperty(key)).toStrictEqual(value)
})
const navigator = await Navigator.discover(url)

test('status is status from response when service returns client or server error', async () => {
const statusCode = faker.internet.httpStatusCode({
types: ['clientError', 'serverError'],
expect(navigator.status).toBe(200)
expect(navigator.resource.getProperty(key)).toStrictEqual(value)
})
const url = 'https://example.com'
server.use(
http.get(url, () => HttpResponse.json({}, { status: statusCode })),
)

const navigator = await Navigator.discover(url)
test('status is status from response when service returns client or server error', async () => {
const statusCode = faker.internet.httpStatusCode({
types: ['clientError', 'serverError'],
})
const url = 'https://example.com'
server.use(
http.get(url, () => HttpResponse.json({}, { status: statusCode })),
)

expect(navigator.status).toBe(statusCode)
})
const navigator = await Navigator.discover(url)

test('status is 200 when getting relation succeeds', async () => {
const relation = faker.lorem.word()
const url = 'https://example.com'
const linkUrl = 'https://example.com/somethings/123'
const resource = {
_links: {
[relation]: { href: linkUrl },
},
}
server.use(http.get(url, () => HttpResponse.json(resource)))
server.use(http.get(linkUrl, () => HttpResponse.json({})))
const discoveryNavigator = await Navigator.discover(url)

const navigator = await discoveryNavigator.get(relation)

expect(navigator.status).toBe(200)
expect(navigator.status).toBe(statusCode)
})
})

test('status is status from response when getting relation returns client or server error', async () => {
const relation = faker.lorem.word()
const url = 'https://example.com'
const linkUrl = 'https://example.com/somethings/123'
const resource = {
_links: {
[relation]: { href: linkUrl },
},
}
const statusCode = faker.internet.httpStatusCode({
types: ['clientError', 'serverError'],
describe('get', () => {
test('status is 200 when getting relation succeeds', async () => {
const relation = faker.lorem.word()
const url = 'https://example.com'
const linkUrl = 'https://example.com/somethings/123'
const resource = {
_links: {
[relation]: { href: linkUrl },
},
}
server.use(http.get(url, () => HttpResponse.json(resource)))
server.use(http.get(linkUrl, () => HttpResponse.json({})))
const discoveryNavigator = await Navigator.discover(url)

const navigator = await discoveryNavigator.get(relation)

expect(navigator.status).toBe(200)
})
server.use(http.get(url, () => HttpResponse.json(resource)))
server.use(
http.get(linkUrl, () => HttpResponse.json({}, { status: statusCode })),
)
const discoveryNavigator = await Navigator.discover(url)

const navigator = await discoveryNavigator.get(relation)

expect(navigator.status).toBe(statusCode)
})
test('status is status from response when getting relation returns client or server error', async () => {
const relation = faker.lorem.word()
const url = 'https://example.com'
const linkUrl = 'https://example.com/somethings/123'
const resource = {
_links: {
[relation]: { href: linkUrl },
},
}
const statusCode = faker.internet.httpStatusCode({
types: ['clientError', 'serverError'],
})
server.use(http.get(url, () => HttpResponse.json(resource)))
server.use(
http.get(linkUrl, () => HttpResponse.json({}, { status: statusCode })),
)
const discoveryNavigator = await Navigator.discover(url)

const navigator = await discoveryNavigator.get(relation)

expect(navigator.status).toBe(statusCode)
})

test('linked resource has property when getting relation succeeds with json with property', async () => {
const relation = faker.lorem.word()
const url = 'https://example.com'
const linkUrl = 'https://example.com/somethings/123'
const resource = {
_links: {
[relation]: { href: linkUrl },
},
}
const key = faker.lorem.word()
const value = randomProperty()
const linkedResource = { [key]: value }
server.use(http.get(url, () => HttpResponse.json(resource)))
server.use(http.get(linkUrl, () => HttpResponse.json(linkedResource)))
const discoveryNavigator = await Navigator.discover(url)

const navigator = await discoveryNavigator.get(relation)

expect(navigator.resource.getProperty(key)).toStrictEqual(value)
})
test('linked resource has property when getting relation succeeds with json with property', async () => {
const relation = faker.lorem.word()
const url = 'https://example.com'
const linkUrl = 'https://example.com/somethings/123'
const resource = {
_links: {
[relation]: { href: linkUrl },
},
}
const key = faker.lorem.word()
const value = randomProperty()
const linkedResource = { [key]: value }
server.use(http.get(url, () => HttpResponse.json(resource)))
server.use(http.get(linkUrl, () => HttpResponse.json(linkedResource)))
const discoveryNavigator = await Navigator.discover(url)

const navigator = await discoveryNavigator.get(relation)

expect(navigator.resource.getProperty(key)).toStrictEqual(value)
})

test('throws when attempting to get link that does not exist on resource', async () => {
const relation = 'non-existent-relation'
const url = 'https://example.com'
server.use(http.get(url, () => HttpResponse.json({})))
const navigator = await Navigator.discover(url)
test('throws when attempting to get link that does not exist on resource', async () => {
const relation = 'non-existent-relation'
const url = 'https://example.com'
server.use(http.get(url, () => HttpResponse.json({})))
const navigator = await Navigator.discover(url)

const action = async () => await navigator.get(relation)
const action = async () => await navigator.get(relation)

expect(action).rejects.toThrow(
`Link with relation '${relation}' does not exist on resource.`,
)
expect(action).rejects.toThrow(
`Link with relation '${relation}' does not exist on resource.`,
)
})
})
})
32 changes: 32 additions & 0 deletions test/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { SetupServer, setupServer } from 'msw/node'
import { RequestHandler } from 'msw/lib/core/handlers/RequestHandler'

export class MockServer {

private readonly server: SetupServer
readonly recordedRequests: Array<Request> = []

constructor(...handlers: Array<RequestHandler>) {
this.server = setupServer(...handlers)
this.server.events.on('request:start', ({request}) => {
this.recordedRequests.push(request)
})
}

listen() {
this.server.listen()
}
close() {
this.server.close()
}

resetHandlers(...nextHandlers: Array<RequestHandler>) {
this.server.resetHandlers(...nextHandlers)
}

use(...handlers: Array<RequestHandler>) {
this.server.use(...handlers)
}


}

0 comments on commit dc641c0

Please sign in to comment.