fix: Move content into to frontend directory

Doing this BEFORE the merge prevents a lot of merge conflicts.

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-11-11 11:16:18 +01:00
parent 4e18ce38f3
commit 762a0a850e
No known key found for this signature in database
GPG key ID: B97799103358209B
1051 changed files with 0 additions and 35 deletions

View file

@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ApiRequestBuilder } from './api-request-builder'
/**
* Builder to construct and execute a call to the HTTP API that contains a body payload.
*
* @param RequestBodyType The type of the request body if applicable.
*/
export abstract class ApiRequestBuilderWithBody<ResponseType, RequestBodyType> extends ApiRequestBuilder<ResponseType> {
/**
* Adds a body part to the API request. If this is called multiple times, only the body of the last invocation will be
* used during the execution of the request.
*
* @param bodyData The data to use as request body.
* @return The API request instance itself for chaining.
*/
withBody(bodyData: BodyInit): this {
this.requestBody = bodyData
return this
}
/**
* Adds a JSON-encoded body part to the API request. This method will set the content-type header appropriately.
*
* @param bodyData The data to use as request body. Will get stringified to JSON.
* @return The API request instance itself for chaining.
* @see withBody
*/
withJsonBody(bodyData: RequestBodyType): this {
this.withHeader('Content-Type', 'application/json')
return this.withBody(JSON.stringify(bodyData))
}
}

View file

@ -0,0 +1,114 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import deepmerge from 'deepmerge'
import { defaultConfig, defaultHeaders } from '../default-config'
import { ApiResponse } from '../api-response'
/**
* Builder to construct and execute a call to the HTTP API.
*
* @param ResponseType The type of the response if applicable.
*/
export abstract class ApiRequestBuilder<ResponseType> {
private readonly targetUrl: string
private overrideExpectedResponseStatus: number | undefined
private customRequestOptions = defaultConfig
private customRequestHeaders = new Headers(defaultHeaders)
private customStatusCodeErrorMapping: Record<number, string> | undefined
protected requestBody: BodyInit | undefined
/**
* Initializes a new API call with the default request options.
*
* @param endpoint The target endpoint without a leading slash.
*/
constructor(endpoint: string) {
this.targetUrl = `api/private/${endpoint}`
}
protected async sendRequestAndVerifyResponse(
httpMethod: RequestInit['method'],
defaultExpectedStatus: number
): Promise<ApiResponse<ResponseType>> {
const response = await fetch(this.targetUrl, {
...this.customRequestOptions,
method: httpMethod,
headers: this.customRequestHeaders,
body: this.requestBody
})
if (this.customStatusCodeErrorMapping && this.customStatusCodeErrorMapping[response.status]) {
throw new Error(this.customStatusCodeErrorMapping[response.status])
}
const expectedStatus = this.overrideExpectedResponseStatus
? this.overrideExpectedResponseStatus
: defaultExpectedStatus
if (response.status !== expectedStatus) {
throw new Error(`Expected response status code ${expectedStatus} but received ${response.status}.`)
}
return new ApiResponse(response)
}
/**
* Adds an HTTP header to the API request. Previous headers with the same name will get overridden on subsequent calls
* with the same name.
*
* @param name The name of the HTTP header to add. Example: 'Content-Type'
* @param value The value of the HTTP header to add. Example: 'text/markdown'
* @return The API request instance itself for chaining.
*/
withHeader(name: string, value: string): this {
this.customRequestHeaders.set(name, value)
return this
}
/**
* Adds custom request options for the underlying fetch request by merging them with the existing options.
*
* @param options The options to set for the fetch request.
* @return The API request instance itself for chaining.
*/
withCustomOptions(options: Partial<Omit<RequestInit, 'method' | 'headers' | 'body'>>): this {
this.customRequestOptions = deepmerge(this.customRequestOptions, options)
return this
}
/**
* Adds a mapping from response status codes to error messages. An error with the specified message will be thrown
* when the status code of the response matches one of the defined ones.
*
* @param mapping The mapping from response status codes to error messages.
* @return The API request instance itself for chaining.
*/
withStatusCodeErrorMapping(mapping: Record<number, string>): this {
this.customStatusCodeErrorMapping = mapping
return this
}
/**
* Sets the expected status code of the response. Can be used to override the default expected status code.
* An error will be thrown when the status code of the response does not match the expected one.
*
* @param expectedCode The expected status code of the response.
* @return The API request instance itself for chaining.
*/
withExpectedStatusCode(expectedCode: number): this {
this.overrideExpectedResponseStatus = expectedCode
return this
}
/**
* Send the prepared API call as a GET request. A default status code of 200 is expected.
*
* @return The API response.
* @throws {Error} when the status code does not match the expected one or is defined as in the custom status code
* error mapping.
*/
abstract sendRequest(): Promise<ApiResponse<ResponseType>>
}

View file

@ -0,0 +1,170 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { expectFetch } from './test-utils/expect-fetch'
import { DeleteApiRequestBuilder } from './delete-api-request-builder'
describe('DeleteApiRequestBuilder', () => {
let originalFetch: typeof global['fetch']
beforeAll(() => {
originalFetch = global.fetch
})
afterAll(() => {
global.fetch = originalFetch
})
describe('sendRequest without body', () => {
it('without headers', async () => {
expectFetch('api/private/test', 204, { method: 'DELETE' })
await new DeleteApiRequestBuilder<string, undefined>('test').sendRequest()
})
it('with single header', async () => {
const expectedHeaders = new Headers()
expectedHeaders.append('test', 'true')
expectFetch('api/private/test', 204, {
method: 'DELETE',
headers: expectedHeaders
})
await new DeleteApiRequestBuilder<string, undefined>('test').withHeader('test', 'true').sendRequest()
})
it('with overriding single header', async () => {
const expectedHeaders = new Headers()
expectedHeaders.append('test', 'false')
expectFetch('api/private/test', 204, {
method: 'DELETE',
headers: expectedHeaders
})
await new DeleteApiRequestBuilder<string, undefined>('test')
.withHeader('test', 'true')
.withHeader('test', 'false')
.sendRequest()
})
it('with multiple different headers', async () => {
const expectedHeaders = new Headers()
expectedHeaders.append('test', 'true')
expectedHeaders.append('test2', 'false')
expectFetch('api/private/test', 204, {
method: 'DELETE',
headers: expectedHeaders
})
await new DeleteApiRequestBuilder<string, undefined>('test')
.withHeader('test', 'true')
.withHeader('test2', 'false')
.sendRequest()
})
})
it('sendRequest with JSON body', async () => {
const expectedHeaders = new Headers()
expectedHeaders.append('Content-Type', 'application/json')
expectFetch('api/private/test', 204, {
method: 'DELETE',
headers: expectedHeaders,
body: '{"test":true,"foo":"bar"}'
})
await new DeleteApiRequestBuilder('test')
.withJsonBody({
test: true,
foo: 'bar'
})
.sendRequest()
})
it('sendRequest with other body', async () => {
expectFetch('api/private/test', 204, {
method: 'DELETE',
body: 'HedgeDoc'
})
await new DeleteApiRequestBuilder('test').withBody('HedgeDoc').sendRequest()
})
it('sendRequest with expected status code', async () => {
expectFetch('api/private/test', 200, { method: 'DELETE' })
await new DeleteApiRequestBuilder<string, undefined>('test').withExpectedStatusCode(200).sendRequest()
})
describe('sendRequest with custom options', () => {
it('with one option', async () => {
expectFetch('api/private/test', 204, {
method: 'DELETE',
cache: 'force-cache'
})
await new DeleteApiRequestBuilder<string, undefined>('test')
.withCustomOptions({
cache: 'force-cache'
})
.sendRequest()
})
it('overriding single option', async () => {
expectFetch('api/private/test', 204, {
method: 'DELETE',
cache: 'no-store'
})
await new DeleteApiRequestBuilder<string, undefined>('test')
.withCustomOptions({
cache: 'force-cache'
})
.withCustomOptions({
cache: 'no-store'
})
.sendRequest()
})
it('with multiple options', async () => {
expectFetch('api/private/test', 204, {
method: 'DELETE',
cache: 'force-cache',
integrity: 'test'
})
await new DeleteApiRequestBuilder<string, undefined>('test')
.withCustomOptions({
cache: 'force-cache',
integrity: 'test'
})
.sendRequest()
})
})
describe('sendRequest with custom error map', () => {
it('for valid status code', async () => {
expectFetch('api/private/test', 204, { method: 'DELETE' })
await new DeleteApiRequestBuilder<string, undefined>('test')
.withStatusCodeErrorMapping({
400: 'noooooo',
401: 'not you!'
})
.sendRequest()
})
it('for invalid status code 1', async () => {
expectFetch('api/private/test', 400, { method: 'DELETE' })
const request = new DeleteApiRequestBuilder<string, undefined>('test')
.withStatusCodeErrorMapping({
400: 'noooooo',
401: 'not you!'
})
.sendRequest()
await expect(request).rejects.toThrow('noooooo')
})
it('for invalid status code 2', async () => {
expectFetch('api/private/test', 401, { method: 'DELETE' })
const request = new DeleteApiRequestBuilder<string, undefined>('test')
.withStatusCodeErrorMapping({
400: 'noooooo',
401: 'not you!'
})
.sendRequest()
await expect(request).rejects.toThrow('not you!')
})
})
})

View file

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { ApiResponse } from '../api-response'
import { ApiRequestBuilderWithBody } from './api-request-builder-with-body'
/**
* Builder to construct a DELETE request to the API.
*
* @param ResponseType The type of the expected response. Defaults to no response body.
* @param RequestBodyType The type of the request body. Defaults to no request body.
* @see ApiRequestBuilder
*/
export class DeleteApiRequestBuilder<ResponseType = void, RequestBodyType = unknown> extends ApiRequestBuilderWithBody<
ResponseType,
RequestBodyType
> {
/**
* @see ApiRequestBuilder#sendRequest
*/
sendRequest(): Promise<ApiResponse<ResponseType>> {
return this.sendRequestAndVerifyResponse('DELETE', 204)
}
}

View file

@ -0,0 +1,146 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { expectFetch } from './test-utils/expect-fetch'
import { GetApiRequestBuilder } from './get-api-request-builder'
describe('GetApiRequestBuilder', () => {
let originalFetch: typeof global['fetch']
beforeAll(() => {
originalFetch = global.fetch
})
afterAll(() => {
global.fetch = originalFetch
})
describe('sendRequest', () => {
it('without headers', async () => {
expectFetch('api/private/test', 200, { method: 'GET' })
await new GetApiRequestBuilder<string>('test').sendRequest()
})
it('with single header', async () => {
const expectedHeaders = new Headers()
expectedHeaders.append('test', 'true')
expectFetch('api/private/test', 200, {
method: 'GET',
headers: expectedHeaders
})
await new GetApiRequestBuilder<string>('test').withHeader('test', 'true').sendRequest()
})
it('with overriding single header', async () => {
const expectedHeaders = new Headers()
expectedHeaders.append('test', 'false')
expectFetch('api/private/test', 200, {
method: 'GET',
headers: expectedHeaders
})
await new GetApiRequestBuilder<string>('test')
.withHeader('test', 'true')
.withHeader('test', 'false')
.sendRequest()
})
it('with multiple different headers', async () => {
const expectedHeaders = new Headers()
expectedHeaders.append('test', 'true')
expectedHeaders.append('test2', 'false')
expectFetch('api/private/test', 200, {
method: 'GET',
headers: expectedHeaders
})
await new GetApiRequestBuilder<string>('test')
.withHeader('test', 'true')
.withHeader('test2', 'false')
.sendRequest()
})
})
it('sendRequest with expected status code', async () => {
expectFetch('api/private/test', 200, { method: 'GET' })
await new GetApiRequestBuilder<string>('test').withExpectedStatusCode(200).sendRequest()
})
describe('sendRequest with custom options', () => {
it('with one option', async () => {
expectFetch('api/private/test', 200, {
method: 'GET',
cache: 'force-cache'
})
await new GetApiRequestBuilder<string>('test')
.withCustomOptions({
cache: 'force-cache'
})
.sendRequest()
})
it('overriding single option', async () => {
expectFetch('api/private/test', 200, {
method: 'GET',
cache: 'no-store'
})
await new GetApiRequestBuilder<string>('test')
.withCustomOptions({
cache: 'force-cache'
})
.withCustomOptions({
cache: 'no-store'
})
.sendRequest()
})
it('with multiple options', async () => {
expectFetch('api/private/test', 200, {
method: 'GET',
cache: 'force-cache',
integrity: 'test'
})
await new GetApiRequestBuilder<string>('test')
.withCustomOptions({
cache: 'force-cache',
integrity: 'test'
})
.sendRequest()
})
})
describe('sendRequest with custom error map', () => {
it('for valid status code', async () => {
expectFetch('api/private/test', 200, { method: 'GET' })
await new GetApiRequestBuilder<string>('test')
.withStatusCodeErrorMapping({
400: 'noooooo',
401: 'not you!'
})
.sendRequest()
})
it('for invalid status code 1', async () => {
expectFetch('api/private/test', 400, { method: 'GET' })
const request = new GetApiRequestBuilder<string>('test')
.withStatusCodeErrorMapping({
400: 'noooooo',
401: 'not you!'
})
.sendRequest()
await expect(request).rejects.toThrow('noooooo')
})
it('for invalid status code 2', async () => {
expectFetch('api/private/test', 401, { method: 'GET' })
const request = new GetApiRequestBuilder<string>('test')
.withStatusCodeErrorMapping({
400: 'noooooo',
401: 'not you!'
})
.sendRequest()
await expect(request).rejects.toThrow('not you!')
})
})
})

View file

@ -0,0 +1,23 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ApiRequestBuilder } from './api-request-builder'
import type { ApiResponse } from '../api-response'
/**
* Builder to construct a GET request to the API.
*
* @param ResponseType The type of the expected response.
* @see ApiRequestBuilder
*/
export class GetApiRequestBuilder<ResponseType> extends ApiRequestBuilder<ResponseType> {
/**
* @see ApiRequestBuilder#sendRequest
*/
sendRequest(): Promise<ApiResponse<ResponseType>> {
return this.sendRequestAndVerifyResponse('GET', 200)
}
}

View file

@ -0,0 +1,171 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { PostApiRequestBuilder } from './post-api-request-builder'
import { expectFetch } from './test-utils/expect-fetch'
describe('PostApiRequestBuilder', () => {
let originalFetch: typeof global['fetch']
beforeAll(() => {
originalFetch = global.fetch
})
afterAll(() => {
global.fetch = originalFetch
})
describe('sendRequest without body', () => {
it('without headers', async () => {
expectFetch('api/private/test', 201, { method: 'POST' })
await new PostApiRequestBuilder<string, undefined>('test').sendRequest()
})
it('with single header', async () => {
const expectedHeaders = new Headers()
expectedHeaders.append('test', 'true')
expectFetch('api/private/test', 201, {
method: 'POST',
headers: expectedHeaders
})
await new PostApiRequestBuilder<string, undefined>('test').withHeader('test', 'true').sendRequest()
})
it('with overriding single header', async () => {
const expectedHeaders = new Headers()
expectedHeaders.append('test', 'false')
expectFetch('api/private/test', 201, {
method: 'POST',
headers: expectedHeaders
})
await new PostApiRequestBuilder<string, undefined>('test')
.withHeader('test', 'true')
.withHeader('test', 'false')
.sendRequest()
})
it('with multiple different headers', async () => {
const expectedHeaders = new Headers()
expectedHeaders.append('test', 'true')
expectedHeaders.append('test2', 'false')
expectFetch('api/private/test', 201, {
method: 'POST',
headers: expectedHeaders
})
await new PostApiRequestBuilder<string, undefined>('test')
.withHeader('test', 'true')
.withHeader('test2', 'false')
.sendRequest()
})
})
it('sendRequest with JSON body', async () => {
const expectedHeaders = new Headers()
expectedHeaders.append('Content-Type', 'application/json')
expectFetch('api/private/test', 201, {
method: 'POST',
headers: expectedHeaders,
body: '{"test":true,"foo":"bar"}'
})
await new PostApiRequestBuilder('test')
.withJsonBody({
test: true,
foo: 'bar'
})
.sendRequest()
})
it('sendRequest with other body', async () => {
expectFetch('api/private/test', 201, {
method: 'POST',
body: 'HedgeDoc'
})
await new PostApiRequestBuilder('test').withBody('HedgeDoc').sendRequest()
})
it('sendRequest with expected status code', async () => {
expectFetch('api/private/test', 200, { method: 'POST' })
await new PostApiRequestBuilder<string, undefined>('test').withExpectedStatusCode(200).sendRequest()
})
describe('sendRequest with custom options', () => {
it('with one option', async () => {
expectFetch('api/private/test', 201, {
method: 'POST',
cache: 'force-cache'
})
await new PostApiRequestBuilder<string, undefined>('test')
.withCustomOptions({
cache: 'force-cache'
})
.sendRequest()
})
it('overriding single option', async () => {
expectFetch('api/private/test', 201, {
method: 'POST',
cache: 'no-store'
})
await new PostApiRequestBuilder<string, undefined>('test')
.withCustomOptions({
cache: 'force-cache'
})
.withCustomOptions({
cache: 'no-store'
})
.sendRequest()
})
it('with multiple options', async () => {
expectFetch('api/private/test', 201, {
method: 'POST',
cache: 'force-cache',
integrity: 'test'
})
await new PostApiRequestBuilder<string, undefined>('test')
.withCustomOptions({
cache: 'force-cache',
integrity: 'test'
})
.sendRequest()
})
})
describe('sendRequest with custom error map', () => {
it('for valid status code', async () => {
expectFetch('api/private/test', 201, { method: 'POST' })
await new PostApiRequestBuilder<string, undefined>('test')
.withStatusCodeErrorMapping({
400: 'noooooo',
401: 'not you!'
})
.sendRequest()
})
it('for invalid status code 1', async () => {
expectFetch('api/private/test', 400, { method: 'POST' })
const request = new PostApiRequestBuilder<string, undefined>('test')
.withStatusCodeErrorMapping({
400: 'noooooo',
401: 'not you!'
})
.sendRequest()
await expect(request).rejects.toThrow('noooooo')
})
it('for invalid status code 2', async () => {
expectFetch('api/private/test', 401, { method: 'POST' })
const request = new PostApiRequestBuilder<string, undefined>('test')
.withStatusCodeErrorMapping({
400: 'noooooo',
401: 'not you!'
})
.sendRequest()
await expect(request).rejects.toThrow('not you!')
})
})
})

View file

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { ApiResponse } from '../api-response'
import { ApiRequestBuilderWithBody } from './api-request-builder-with-body'
/**
* Builder to construct a POST request to the API.
*
* @param ResponseType The type of the expected response.
* @param RequestBodyType The type of the request body
* @see ApiRequestBuilder
*/
export class PostApiRequestBuilder<ResponseType, RequestBodyType> extends ApiRequestBuilderWithBody<
ResponseType,
RequestBodyType
> {
/**
* @see ApiRequestBuilder#sendRequest
*/
sendRequest(): Promise<ApiResponse<ResponseType>> {
return this.sendRequestAndVerifyResponse('POST', 201)
}
}

View file

@ -0,0 +1,171 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { expectFetch } from './test-utils/expect-fetch'
import { PutApiRequestBuilder } from './put-api-request-builder'
describe('PutApiRequestBuilder', () => {
let originalFetch: typeof global['fetch']
beforeAll(() => {
originalFetch = global.fetch
})
afterAll(() => {
global.fetch = originalFetch
})
describe('sendRequest without body', () => {
it('without headers', async () => {
expectFetch('api/private/test', 200, { method: 'PUT' })
await new PutApiRequestBuilder<string, undefined>('test').sendRequest()
})
it('with single header', async () => {
const expectedHeaders = new Headers()
expectedHeaders.append('test', 'true')
expectFetch('api/private/test', 200, {
method: 'PUT',
headers: expectedHeaders
})
await new PutApiRequestBuilder<string, undefined>('test').withHeader('test', 'true').sendRequest()
})
it('with overriding single header', async () => {
const expectedHeaders = new Headers()
expectedHeaders.append('test', 'false')
expectFetch('api/private/test', 200, {
method: 'PUT',
headers: expectedHeaders
})
await new PutApiRequestBuilder<string, undefined>('test')
.withHeader('test', 'true')
.withHeader('test', 'false')
.sendRequest()
})
it('with multiple different headers', async () => {
const expectedHeaders = new Headers()
expectedHeaders.append('test', 'true')
expectedHeaders.append('test2', 'false')
expectFetch('api/private/test', 200, {
method: 'PUT',
headers: expectedHeaders
})
await new PutApiRequestBuilder<string, undefined>('test')
.withHeader('test', 'true')
.withHeader('test2', 'false')
.sendRequest()
})
})
it('sendRequest with JSON body', async () => {
const expectedHeaders = new Headers()
expectedHeaders.append('Content-Type', 'application/json')
expectFetch('api/private/test', 200, {
method: 'PUT',
headers: expectedHeaders,
body: '{"test":true,"foo":"bar"}'
})
await new PutApiRequestBuilder('test')
.withJsonBody({
test: true,
foo: 'bar'
})
.sendRequest()
})
it('sendRequest with other body', async () => {
expectFetch('api/private/test', 200, {
method: 'PUT',
body: 'HedgeDoc'
})
await new PutApiRequestBuilder('test').withBody('HedgeDoc').sendRequest()
})
it('sendRequest with expected status code', async () => {
expectFetch('api/private/test', 200, { method: 'PUT' })
await new PutApiRequestBuilder<string, undefined>('test').withExpectedStatusCode(200).sendRequest()
})
describe('sendRequest with custom options', () => {
it('with one option', async () => {
expectFetch('api/private/test', 200, {
method: 'PUT',
cache: 'force-cache'
})
await new PutApiRequestBuilder<string, undefined>('test')
.withCustomOptions({
cache: 'force-cache'
})
.sendRequest()
})
it('overriding single option', async () => {
expectFetch('api/private/test', 200, {
method: 'PUT',
cache: 'no-store'
})
await new PutApiRequestBuilder<string, undefined>('test')
.withCustomOptions({
cache: 'force-cache'
})
.withCustomOptions({
cache: 'no-store'
})
.sendRequest()
})
it('with multiple options', async () => {
expectFetch('api/private/test', 200, {
method: 'PUT',
cache: 'force-cache',
integrity: 'test'
})
await new PutApiRequestBuilder<string, undefined>('test')
.withCustomOptions({
cache: 'force-cache',
integrity: 'test'
})
.sendRequest()
})
})
describe('sendRequest with custom error map', () => {
it('for valid status code', async () => {
expectFetch('api/private/test', 200, { method: 'PUT' })
await new PutApiRequestBuilder<string, undefined>('test')
.withStatusCodeErrorMapping({
400: 'noooooo',
401: 'not you!'
})
.sendRequest()
})
it('for invalid status code 1', async () => {
expectFetch('api/private/test', 400, { method: 'PUT' })
const request = new PutApiRequestBuilder<string, undefined>('test')
.withStatusCodeErrorMapping({
400: 'noooooo',
401: 'not you!'
})
.sendRequest()
await expect(request).rejects.toThrow('noooooo')
})
it('for invalid status code 2', async () => {
expectFetch('api/private/test', 401, { method: 'PUT' })
const request = new PutApiRequestBuilder<string, undefined>('test')
.withStatusCodeErrorMapping({
400: 'noooooo',
401: 'not you!'
})
.sendRequest()
await expect(request).rejects.toThrow('not you!')
})
})
})

View file

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { ApiResponse } from '../api-response'
import { ApiRequestBuilderWithBody } from './api-request-builder-with-body'
/**
* Builder to construct a PUT request to the API.
*
* @param ResponseType The type of the expected response.
* @param RequestBodyType The type of the request body
* @see ApiRequestBuilder
*/
export class PutApiRequestBuilder<ResponseType, RequestBodyType> extends ApiRequestBuilderWithBody<
ResponseType,
RequestBodyType
> {
/**
* @see ApiRequestBuilder#sendRequest
*/
sendRequest(): Promise<ApiResponse<ResponseType>> {
return this.sendRequestAndVerifyResponse('PUT', 200)
}
}

View file

@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defaultConfig } from '../../default-config'
import { Mock } from 'ts-mockery'
/**
* Mock fetch api for tests.
* Check that the given url and options are present in the request and return the given status code.
*
* @param expectedUrl the url that should be requested
* @param requestStatusCode the status code the mocked request should return
* @param expectedOptions additional options
*/
export const expectFetch = (expectedUrl: string, requestStatusCode: number, expectedOptions: RequestInit): void => {
global.fetch = jest.fn((fetchUrl: RequestInfo | URL, fetchOptions?: RequestInit): Promise<Response> => {
expect(fetchUrl).toEqual(expectedUrl)
expect(fetchOptions).toStrictEqual({
...defaultConfig,
body: undefined,
headers: new Headers(),
...expectedOptions
})
return Promise.resolve(
Mock.of<Response>({
status: requestStatusCode
})
)
})
}