mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-14 15:14:56 -04:00
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:
parent
4e18ce38f3
commit
762a0a850e
1051 changed files with 0 additions and 35 deletions
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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>>
|
||||
}
|
|
@ -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!')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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!')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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!')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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!')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue