mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-21 17:26:29 -05:00
fix: remove subpath support for HD_BASE_URL
With this commit we drop the subpath support which results in the constraint that HedgeDoc must always run on the root of a domain. This makes a lot of things in testing, rendering and security much easier. Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
7401791ec8
commit
dccd58f0c1
32 changed files with 111 additions and 116 deletions
|
@ -180,11 +180,11 @@ describe('appConfig', () => {
|
||||||
restore();
|
restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('when given a base url with path but no trailing slash in HD_BASE_URL', async () => {
|
it('when given a base url with subdirectory in HD_BASE_URL', async () => {
|
||||||
const restore = mockedEnv(
|
const restore = mockedEnv(
|
||||||
{
|
{
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
HD_BASE_URL: 'https://example.org/a',
|
HD_BASE_URL: 'https://example.org/subdirectory/',
|
||||||
HD_LOGLEVEL: loglevel,
|
HD_LOGLEVEL: loglevel,
|
||||||
/* eslint-enable @typescript-eslint/naming-convention */
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
},
|
},
|
||||||
|
@ -193,7 +193,7 @@ describe('appConfig', () => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
expect(() => appConfig()).toThrow(
|
expect(() => appConfig()).toThrow(
|
||||||
'"HD_BASE_URL" must end with a trailing slash',
|
'"HD_BASE_URL" must not contain a subdirectory',
|
||||||
);
|
);
|
||||||
restore();
|
restore();
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
MissingTrailingSlashError,
|
NoSubdirectoryAllowedError,
|
||||||
parseUrl,
|
parseUrl,
|
||||||
WrongProtocolError,
|
WrongProtocolError,
|
||||||
} from '@hedgedoc/commons';
|
} from '@hedgedoc/commons';
|
||||||
|
@ -23,15 +23,15 @@ export interface AppConfig {
|
||||||
persistInterval: number;
|
persistInterval: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateUrlWithTrailingSlash(
|
function validateUrl(
|
||||||
value: string,
|
value: string,
|
||||||
helpers: CustomHelpers,
|
helpers: CustomHelpers,
|
||||||
): string | ErrorReport {
|
): string | ErrorReport {
|
||||||
try {
|
try {
|
||||||
return parseUrl(value).isPresent() ? value : helpers.error('string.uri');
|
return parseUrl(value).isPresent() ? value : helpers.error('string.uri');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof MissingTrailingSlashError) {
|
if (error instanceof NoSubdirectoryAllowedError) {
|
||||||
return helpers.error('url.missingTrailingSlash');
|
return helpers.error('url.noSubDirectoryAllowed');
|
||||||
} else if (error instanceof WrongProtocolError) {
|
} else if (error instanceof WrongProtocolError) {
|
||||||
return helpers.error('url.wrongProtocol');
|
return helpers.error('url.wrongProtocol');
|
||||||
} else {
|
} else {
|
||||||
|
@ -41,11 +41,9 @@ function validateUrlWithTrailingSlash(
|
||||||
}
|
}
|
||||||
|
|
||||||
const schema = Joi.object({
|
const schema = Joi.object({
|
||||||
baseUrl: Joi.string()
|
baseUrl: Joi.string().custom(validateUrl).label('HD_BASE_URL'),
|
||||||
.custom(validateUrlWithTrailingSlash)
|
|
||||||
.label('HD_BASE_URL'),
|
|
||||||
rendererBaseUrl: Joi.string()
|
rendererBaseUrl: Joi.string()
|
||||||
.custom(validateUrlWithTrailingSlash)
|
.custom(validateUrl)
|
||||||
.default(Joi.ref('baseUrl'))
|
.default(Joi.ref('baseUrl'))
|
||||||
.optional()
|
.optional()
|
||||||
.label('HD_RENDERER_BASE_URL'),
|
.label('HD_RENDERER_BASE_URL'),
|
||||||
|
@ -69,7 +67,7 @@ const schema = Joi.object({
|
||||||
.label('HD_PERSIST_INTERVAL'),
|
.label('HD_PERSIST_INTERVAL'),
|
||||||
}).messages({
|
}).messages({
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
'url.missingTrailingSlash': '{{#label}} must end with a trailing slash',
|
'url.noSubDirectoryAllowed': '{{#label}} must not contain a subdirectory',
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
'url.wrongProtocol': '{{#label}} protocol must be HTTP or HTTPS',
|
'url.wrongProtocol': '{{#label}} protocol must be HTTP or HTTPS',
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class MissingTrailingSlashError extends Error {
|
export class NoSubdirectoryAllowedError extends Error {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("Path doesn't end with a trailing slash")
|
super('Subdirectories are not allowed')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,4 +5,4 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { parseUrl } from './parse-url.js'
|
export { parseUrl } from './parse-url.js'
|
||||||
export { MissingTrailingSlashError, WrongProtocolError } from './errors.js'
|
export { NoSubdirectoryAllowedError, WrongProtocolError } from './errors.js'
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { MissingTrailingSlashError, WrongProtocolError } from './errors.js'
|
import { NoSubdirectoryAllowedError, WrongProtocolError } from './errors.js'
|
||||||
import { parseUrl } from './parse-url.js'
|
import { parseUrl } from './parse-url.js'
|
||||||
import { describe, expect, it } from '@jest/globals'
|
import { describe, expect, it } from '@jest/globals'
|
||||||
|
|
||||||
|
@ -44,14 +44,14 @@ describe('validate url', () => {
|
||||||
'http://example.org/'
|
'http://example.org/'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
it('accepts urls with with subpath and trailing slash', () => {
|
it('declines urls with with subpath and trailing slash', () => {
|
||||||
expect(parseUrl('http://example.org/asd/').get().toString()).toEqual(
|
expect(() =>
|
||||||
'http://example.org/asd/'
|
parseUrl('http://example.org/asd/').get().toString()
|
||||||
)
|
).toThrow(NoSubdirectoryAllowedError)
|
||||||
})
|
})
|
||||||
it("doesn't accept urls with with subpath and without trailing slash", () => {
|
it('declines urls with with subpath and without trailing slash', () => {
|
||||||
expect(() => parseUrl('http://example.org/asd').get().toString()).toThrow(
|
expect(() => parseUrl('http://example.org/asd').get().toString()).toThrow(
|
||||||
MissingTrailingSlashError
|
NoSubdirectoryAllowedError
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { MissingTrailingSlashError, WrongProtocolError } from './errors.js'
|
import { NoSubdirectoryAllowedError, WrongProtocolError } from './errors.js'
|
||||||
import { Optional } from '@mrdrogdrog/optional'
|
import { Optional } from '@mrdrogdrog/optional'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,7 +12,7 @@ import { Optional } from '@mrdrogdrog/optional'
|
||||||
* @param {String | undefined} url the raw url
|
* @param {String | undefined} url the raw url
|
||||||
* @return An {@link Optional} that contains the parsed URL or is empty if the raw value isn't a valid URL
|
* @return An {@link Optional} that contains the parsed URL or is empty if the raw value isn't a valid URL
|
||||||
* @throws WrongProtocolError if the protocol of the URL isn't either http nor https
|
* @throws WrongProtocolError if the protocol of the URL isn't either http nor https
|
||||||
* @throws MissingTrailingSlashError if the URL has a path that doesn't end with a trailing slash
|
* @throws NoSubdirectoryAllowedError if the URL has a path that doesn't end with a trailing slash
|
||||||
*/
|
*/
|
||||||
export function parseUrl(url: string | undefined): Optional<URL> {
|
export function parseUrl(url: string | undefined): Optional<URL> {
|
||||||
return createOptionalUrl(url)
|
return createOptionalUrl(url)
|
||||||
|
@ -21,8 +21,8 @@ export function parseUrl(url: string | undefined): Optional<URL> {
|
||||||
() => new WrongProtocolError()
|
() => new WrongProtocolError()
|
||||||
)
|
)
|
||||||
.guard(
|
.guard(
|
||||||
(value) => value.pathname.endsWith('/'),
|
(value) => value.pathname === '/',
|
||||||
() => new MissingTrailingSlashError()
|
() => new NoSubdirectoryAllowedError()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ describe('File upload', () => {
|
||||||
cy.intercept(
|
cy.intercept(
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'api/private/media'
|
url: '/api/private/media'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
statusCode: 201,
|
statusCode: 201,
|
||||||
|
@ -74,7 +74,7 @@ describe('File upload', () => {
|
||||||
cy.intercept(
|
cy.intercept(
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'api/private/media'
|
url: '/api/private/media'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
statusCode: 400
|
statusCode: 400
|
||||||
|
|
|
@ -25,7 +25,7 @@ describe('History', () => {
|
||||||
describe('is as given when not empty', () => {
|
describe('is as given when not empty', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.clearLocalStorage('history')
|
cy.clearLocalStorage('history')
|
||||||
cy.intercept('GET', 'api/private/me/history', {
|
cy.intercept('GET', '/api/private/me/history', {
|
||||||
body: [
|
body: [
|
||||||
{
|
{
|
||||||
identifier: 'cypress',
|
identifier: 'cypress',
|
||||||
|
@ -52,7 +52,7 @@ describe('History', () => {
|
||||||
describe('is untitled when not empty', () => {
|
describe('is untitled when not empty', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.clearLocalStorage('history')
|
cy.clearLocalStorage('history')
|
||||||
cy.intercept('GET', 'api/private/me/history', {
|
cy.intercept('GET', '/api/private/me/history', {
|
||||||
body: [
|
body: [
|
||||||
{
|
{
|
||||||
identifier: 'cypress-no-title',
|
identifier: 'cypress-no-title',
|
||||||
|
@ -85,7 +85,7 @@ describe('History', () => {
|
||||||
|
|
||||||
describe('working', () => {
|
describe('working', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.intercept('PUT', 'api/private/me/history/features', (req) => {
|
cy.intercept('PUT', '/api/private/me/history/features', (req) => {
|
||||||
req.reply(200, req.body)
|
req.reply(200, req.body)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -115,7 +115,7 @@ describe('History', () => {
|
||||||
|
|
||||||
describe('failing', () => {
|
describe('failing', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.intercept('PUT', 'api/private/me/history/features', {
|
cy.intercept('PUT', '/api/private/me/history/features', {
|
||||||
statusCode: 401
|
statusCode: 401
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -143,7 +143,7 @@ describe('History', () => {
|
||||||
describe('Import', () => {
|
describe('Import', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.clearLocalStorage('history')
|
cy.clearLocalStorage('history')
|
||||||
cy.intercept('GET', 'api/private/me/history', {
|
cy.intercept('GET', '/api/private/me/history', {
|
||||||
body: []
|
body: []
|
||||||
})
|
})
|
||||||
cy.visitHistory()
|
cy.visitHistory()
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
describe('Intro page', () => {
|
describe('Intro page', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.intercept('public/intro.md', 'test content')
|
cy.intercept('/public/intro.md', 'test content')
|
||||||
cy.visitHome()
|
cy.visitHome()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ describe('Intro page', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("won't show anything if no content was found", () => {
|
it("won't show anything if no content was found", () => {
|
||||||
cy.intercept('public/intro.md', {
|
cy.intercept('/public/intro.md', {
|
||||||
statusCode: 404
|
statusCode: 404
|
||||||
})
|
})
|
||||||
cy.visitHome()
|
cy.visitHome()
|
||||||
|
|
|
@ -12,13 +12,13 @@ const motdMockHtml = 'This is the <strong>mock</strong> Motd call'
|
||||||
describe('Motd', () => {
|
describe('Motd', () => {
|
||||||
it("shows, dismisses and won't show again a motd modal", () => {
|
it("shows, dismisses and won't show again a motd modal", () => {
|
||||||
localStorage.removeItem(MOTD_LOCAL_STORAGE_KEY)
|
localStorage.removeItem(MOTD_LOCAL_STORAGE_KEY)
|
||||||
cy.intercept('GET', 'public/motd.md', {
|
cy.intercept('GET', '/public/motd.md', {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
headers: { 'Last-Modified': MOCK_LAST_MODIFIED },
|
headers: { 'Last-Modified': MOCK_LAST_MODIFIED },
|
||||||
body: motdMockContent
|
body: motdMockContent
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.intercept('HEAD', 'public/motd.md', {
|
cy.intercept('HEAD', '/public/motd.md', {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
headers: { 'Last-Modified': MOCK_LAST_MODIFIED }
|
headers: { 'Last-Modified': MOCK_LAST_MODIFIED }
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,7 +8,7 @@ describe('profile page', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.intercept(
|
cy.intercept(
|
||||||
{
|
{
|
||||||
url: 'api/private/tokens',
|
url: '/api/private/tokens',
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -25,7 +25,7 @@ describe('profile page', () => {
|
||||||
)
|
)
|
||||||
cy.intercept(
|
cy.intercept(
|
||||||
{
|
{
|
||||||
url: 'api/private/tokens',
|
url: '/api/private/tokens',
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -42,7 +42,7 @@ describe('profile page', () => {
|
||||||
)
|
)
|
||||||
cy.intercept(
|
cy.intercept(
|
||||||
{
|
{
|
||||||
url: 'api/private/tokens/cypress',
|
url: '/api/private/tokens/cypress',
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -13,7 +13,7 @@ declare namespace Cypress {
|
||||||
|
|
||||||
export const branding = {
|
export const branding = {
|
||||||
name: 'DEMO Corp',
|
name: 'DEMO Corp',
|
||||||
logo: 'public/img/demo.png'
|
logo: '/public/img/demo.png'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const authProviders = [
|
export const authProviders = [
|
||||||
|
@ -80,7 +80,7 @@ export const config = {
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add('loadConfig', (additionalConfig?: Partial<typeof config>) => {
|
Cypress.Commands.add('loadConfig', (additionalConfig?: Partial<typeof config>) => {
|
||||||
return cy.intercept('api/private/config', {
|
return cy.intercept('/api/private/config', {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
body: {
|
body: {
|
||||||
...config,
|
...config,
|
||||||
|
@ -92,11 +92,11 @@ Cypress.Commands.add('loadConfig', (additionalConfig?: Partial<typeof config>) =
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.loadConfig()
|
cy.loadConfig()
|
||||||
|
|
||||||
cy.intercept('GET', 'public/motd.md', {
|
cy.intercept('GET', '/public/motd.md', {
|
||||||
body: '404 Not Found!',
|
body: '404 Not Found!',
|
||||||
statusCode: 404
|
statusCode: 404
|
||||||
})
|
})
|
||||||
cy.intercept('HEAD', 'public/motd.md', {
|
cy.intercept('HEAD', '/public/motd.md', {
|
||||||
statusCode: 404
|
statusCode: 404
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,6 +2,6 @@
|
||||||
What you see is an UI-Test! It's filled with dummy data, not connected to a backend and no data will be saved.
|
What you see is an UI-Test! It's filled with dummy data, not connected to a backend and no data will be saved.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
![HedgeDoc Screenshot](public/screenshot.png)
|
![HedgeDoc Screenshot](/public/screenshot.png)
|
||||||
|
|
||||||
[![Deployed using netlify](https://www.netlify.com/img/global/badges/netlify-color-accent.svg)](https://www.netlify.com)
|
[![Deployed using netlify](https://www.netlify.com/img/global/badges/netlify-color-accent.svg)](https://www.netlify.com)
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
What you see is an UI-Test! It's filled with dummy data, not connected to a backend and no data will be saved.
|
What you see is an UI-Test! It's filled with dummy data, not connected to a backend and no data will be saved.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
![HedgeDoc Screenshot](public/screenshot.png)
|
![HedgeDoc Screenshot](/public/screenshot.png)
|
||||||
|
|
|
@ -27,7 +27,7 @@ export abstract class ApiRequestBuilder<ResponseType> {
|
||||||
* @param baseUrl An optional base URL that is used for the endpoint
|
* @param baseUrl An optional base URL that is used for the endpoint
|
||||||
*/
|
*/
|
||||||
constructor(endpoint: string, baseUrl?: string) {
|
constructor(endpoint: string, baseUrl?: string) {
|
||||||
this.targetUrl = `${baseUrl ?? ''}api/private/${endpoint}`
|
this.targetUrl = `${baseUrl ?? '/'}api/private/${endpoint}`
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async sendRequestAndVerifyResponse(httpMethod: RequestInit['method']): Promise<ApiResponse<ResponseType>> {
|
protected async sendRequestAndVerifyResponse(httpMethod: RequestInit['method']): Promise<ApiResponse<ResponseType>> {
|
||||||
|
|
|
@ -26,14 +26,14 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
|
|
||||||
describe('sendRequest without body', () => {
|
describe('sendRequest without body', () => {
|
||||||
it('without headers', async () => {
|
it('without headers', async () => {
|
||||||
expectFetch('api/private/test', 204, { method: 'DELETE' })
|
expectFetch('/api/private/test', 204, { method: 'DELETE' })
|
||||||
await new DeleteApiRequestBuilder<string, undefined>('test').sendRequest()
|
await new DeleteApiRequestBuilder<string, undefined>('test').sendRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with single header', async () => {
|
it('with single header', async () => {
|
||||||
const expectedHeaders = new Headers()
|
const expectedHeaders = new Headers()
|
||||||
expectedHeaders.append('test', 'true')
|
expectedHeaders.append('test', 'true')
|
||||||
expectFetch('api/private/test', 204, {
|
expectFetch('/api/private/test', 204, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
|
@ -43,7 +43,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
it('with overriding single header', async () => {
|
it('with overriding single header', async () => {
|
||||||
const expectedHeaders = new Headers()
|
const expectedHeaders = new Headers()
|
||||||
expectedHeaders.append('test', 'false')
|
expectedHeaders.append('test', 'false')
|
||||||
expectFetch('api/private/test', 204, {
|
expectFetch('/api/private/test', 204, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
|
@ -57,7 +57,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
const expectedHeaders = new Headers()
|
const expectedHeaders = new Headers()
|
||||||
expectedHeaders.append('test', 'true')
|
expectedHeaders.append('test', 'true')
|
||||||
expectedHeaders.append('test2', 'false')
|
expectedHeaders.append('test2', 'false')
|
||||||
expectFetch('api/private/test', 204, {
|
expectFetch('/api/private/test', 204, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
|
@ -72,7 +72,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
const expectedHeaders = new Headers()
|
const expectedHeaders = new Headers()
|
||||||
expectedHeaders.append('Content-Type', 'application/json')
|
expectedHeaders.append('Content-Type', 'application/json')
|
||||||
|
|
||||||
expectFetch('api/private/test', 204, {
|
expectFetch('/api/private/test', 204, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: expectedHeaders,
|
headers: expectedHeaders,
|
||||||
body: '{"test":true,"foo":"bar"}'
|
body: '{"test":true,"foo":"bar"}'
|
||||||
|
@ -86,7 +86,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sendRequest with other body', async () => {
|
it('sendRequest with other body', async () => {
|
||||||
expectFetch('api/private/test', 204, {
|
expectFetch('/api/private/test', 204, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
body: 'HedgeDoc'
|
body: 'HedgeDoc'
|
||||||
})
|
})
|
||||||
|
@ -95,7 +95,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
|
|
||||||
describe('sendRequest with custom options', () => {
|
describe('sendRequest with custom options', () => {
|
||||||
it('with one option', async () => {
|
it('with one option', async () => {
|
||||||
expectFetch('api/private/test', 204, {
|
expectFetch('/api/private/test', 204, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
cache: 'force-cache'
|
cache: 'force-cache'
|
||||||
})
|
})
|
||||||
|
@ -107,7 +107,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('overriding single option', async () => {
|
it('overriding single option', async () => {
|
||||||
expectFetch('api/private/test', 204, {
|
expectFetch('/api/private/test', 204, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
cache: 'no-store'
|
cache: 'no-store'
|
||||||
})
|
})
|
||||||
|
@ -122,7 +122,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with multiple options', async () => {
|
it('with multiple options', async () => {
|
||||||
expectFetch('api/private/test', 204, {
|
expectFetch('/api/private/test', 204, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
cache: 'force-cache',
|
cache: 'force-cache',
|
||||||
integrity: 'test'
|
integrity: 'test'
|
||||||
|
@ -138,13 +138,13 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
|
|
||||||
describe('failing sendRequest', () => {
|
describe('failing sendRequest', () => {
|
||||||
it('without backend provided error name or error message', async () => {
|
it('without backend provided error name or error message', async () => {
|
||||||
expectFetch('api/private/test', 400, { method: 'DELETE' })
|
expectFetch('/api/private/test', 400, { method: 'DELETE' })
|
||||||
const request = new DeleteApiRequestBuilder<string>('test').sendRequest()
|
const request = new DeleteApiRequestBuilder<string>('test').sendRequest()
|
||||||
await expect(request).rejects.toEqual(new ApiError(400, undefined, undefined))
|
await expect(request).rejects.toEqual(new ApiError(400, undefined, undefined))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with backend error name and error message', async () => {
|
it('with backend error name and error message', async () => {
|
||||||
expectFetch('api/private/test', 400, { method: 'DELETE' }, {
|
expectFetch('/api/private/test', 400, { method: 'DELETE' }, {
|
||||||
message: 'The API has exploded!',
|
message: 'The API has exploded!',
|
||||||
name: 'testExplosion'
|
name: 'testExplosion'
|
||||||
} as ApiErrorResponse)
|
} as ApiErrorResponse)
|
||||||
|
@ -153,7 +153,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with another status code than 400', async () => {
|
it('with another status code than 400', async () => {
|
||||||
expectFetch('api/private/test', 401, { method: 'DELETE' }, {
|
expectFetch('/api/private/test', 401, { method: 'DELETE' }, {
|
||||||
message: 'The API has exploded!',
|
message: 'The API has exploded!',
|
||||||
name: 'testExplosion'
|
name: 'testExplosion'
|
||||||
} as ApiErrorResponse)
|
} as ApiErrorResponse)
|
||||||
|
|
|
@ -26,14 +26,14 @@ describe('GetApiRequestBuilder', () => {
|
||||||
|
|
||||||
describe('sendRequest', () => {
|
describe('sendRequest', () => {
|
||||||
it('without headers', async () => {
|
it('without headers', async () => {
|
||||||
expectFetch('api/private/test', 200, { method: 'GET' })
|
expectFetch('/api/private/test', 200, { method: 'GET' })
|
||||||
await new GetApiRequestBuilder<string>('test').sendRequest()
|
await new GetApiRequestBuilder<string>('test').sendRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with single header', async () => {
|
it('with single header', async () => {
|
||||||
const expectedHeaders = new Headers()
|
const expectedHeaders = new Headers()
|
||||||
expectedHeaders.append('test', 'true')
|
expectedHeaders.append('test', 'true')
|
||||||
expectFetch('api/private/test', 200, {
|
expectFetch('/api/private/test', 200, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
|
@ -43,7 +43,7 @@ describe('GetApiRequestBuilder', () => {
|
||||||
it('with overriding single header', async () => {
|
it('with overriding single header', async () => {
|
||||||
const expectedHeaders = new Headers()
|
const expectedHeaders = new Headers()
|
||||||
expectedHeaders.append('test', 'false')
|
expectedHeaders.append('test', 'false')
|
||||||
expectFetch('api/private/test', 200, {
|
expectFetch('/api/private/test', 200, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
|
@ -57,7 +57,7 @@ describe('GetApiRequestBuilder', () => {
|
||||||
const expectedHeaders = new Headers()
|
const expectedHeaders = new Headers()
|
||||||
expectedHeaders.append('test', 'true')
|
expectedHeaders.append('test', 'true')
|
||||||
expectedHeaders.append('test2', 'false')
|
expectedHeaders.append('test2', 'false')
|
||||||
expectFetch('api/private/test', 200, {
|
expectFetch('/api/private/test', 200, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
|
@ -70,7 +70,7 @@ describe('GetApiRequestBuilder', () => {
|
||||||
|
|
||||||
describe('sendRequest with custom options', () => {
|
describe('sendRequest with custom options', () => {
|
||||||
it('with one option', async () => {
|
it('with one option', async () => {
|
||||||
expectFetch('api/private/test', 200, {
|
expectFetch('/api/private/test', 200, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
cache: 'force-cache'
|
cache: 'force-cache'
|
||||||
})
|
})
|
||||||
|
@ -82,7 +82,7 @@ describe('GetApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('overriding single option', async () => {
|
it('overriding single option', async () => {
|
||||||
expectFetch('api/private/test', 200, {
|
expectFetch('/api/private/test', 200, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
cache: 'no-store'
|
cache: 'no-store'
|
||||||
})
|
})
|
||||||
|
@ -97,7 +97,7 @@ describe('GetApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with multiple options', async () => {
|
it('with multiple options', async () => {
|
||||||
expectFetch('api/private/test', 200, {
|
expectFetch('/api/private/test', 200, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
cache: 'force-cache',
|
cache: 'force-cache',
|
||||||
integrity: 'test'
|
integrity: 'test'
|
||||||
|
@ -113,13 +113,13 @@ describe('GetApiRequestBuilder', () => {
|
||||||
|
|
||||||
describe('failing sendRequest', () => {
|
describe('failing sendRequest', () => {
|
||||||
it('without backend provided error name or error message', async () => {
|
it('without backend provided error name or error message', async () => {
|
||||||
expectFetch('api/private/test', 400, { method: 'GET' })
|
expectFetch('/api/private/test', 400, { method: 'GET' })
|
||||||
const request = new GetApiRequestBuilder<string>('test').sendRequest()
|
const request = new GetApiRequestBuilder<string>('test').sendRequest()
|
||||||
await expect(request).rejects.toEqual(new ApiError(400, undefined, undefined))
|
await expect(request).rejects.toEqual(new ApiError(400, undefined, undefined))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with backend error name and error message', async () => {
|
it('with backend error name and error message', async () => {
|
||||||
expectFetch('api/private/test', 400, { method: 'GET' }, {
|
expectFetch('/api/private/test', 400, { method: 'GET' }, {
|
||||||
message: 'The API has exploded!',
|
message: 'The API has exploded!',
|
||||||
name: 'testExplosion'
|
name: 'testExplosion'
|
||||||
} as ApiErrorResponse)
|
} as ApiErrorResponse)
|
||||||
|
@ -128,7 +128,7 @@ describe('GetApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with another status code than 400', async () => {
|
it('with another status code than 400', async () => {
|
||||||
expectFetch('api/private/test', 401, { method: 'GET' }, {
|
expectFetch('/api/private/test', 401, { method: 'GET' }, {
|
||||||
message: 'The API has exploded!',
|
message: 'The API has exploded!',
|
||||||
name: 'testExplosion'
|
name: 'testExplosion'
|
||||||
} as ApiErrorResponse)
|
} as ApiErrorResponse)
|
||||||
|
|
|
@ -26,14 +26,14 @@ describe('PostApiRequestBuilder', () => {
|
||||||
|
|
||||||
describe('sendRequest without body', () => {
|
describe('sendRequest without body', () => {
|
||||||
it('without headers', async () => {
|
it('without headers', async () => {
|
||||||
expectFetch('api/private/test', 201, { method: 'POST' })
|
expectFetch('/api/private/test', 201, { method: 'POST' })
|
||||||
await new PostApiRequestBuilder<string, undefined>('test').sendRequest()
|
await new PostApiRequestBuilder<string, undefined>('test').sendRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with single header', async () => {
|
it('with single header', async () => {
|
||||||
const expectedHeaders = new Headers()
|
const expectedHeaders = new Headers()
|
||||||
expectedHeaders.append('test', 'true')
|
expectedHeaders.append('test', 'true')
|
||||||
expectFetch('api/private/test', 201, {
|
expectFetch('/api/private/test', 201, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
|
@ -43,7 +43,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
it('with overriding single header', async () => {
|
it('with overriding single header', async () => {
|
||||||
const expectedHeaders = new Headers()
|
const expectedHeaders = new Headers()
|
||||||
expectedHeaders.append('test', 'false')
|
expectedHeaders.append('test', 'false')
|
||||||
expectFetch('api/private/test', 201, {
|
expectFetch('/api/private/test', 201, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
|
@ -57,7 +57,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
const expectedHeaders = new Headers()
|
const expectedHeaders = new Headers()
|
||||||
expectedHeaders.append('test', 'true')
|
expectedHeaders.append('test', 'true')
|
||||||
expectedHeaders.append('test2', 'false')
|
expectedHeaders.append('test2', 'false')
|
||||||
expectFetch('api/private/test', 201, {
|
expectFetch('/api/private/test', 201, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
|
@ -72,7 +72,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
const expectedHeaders = new Headers()
|
const expectedHeaders = new Headers()
|
||||||
expectedHeaders.append('Content-Type', 'application/json')
|
expectedHeaders.append('Content-Type', 'application/json')
|
||||||
|
|
||||||
expectFetch('api/private/test', 201, {
|
expectFetch('/api/private/test', 201, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: expectedHeaders,
|
headers: expectedHeaders,
|
||||||
body: '{"test":true,"foo":"bar"}'
|
body: '{"test":true,"foo":"bar"}'
|
||||||
|
@ -86,7 +86,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sendRequest with other body', async () => {
|
it('sendRequest with other body', async () => {
|
||||||
expectFetch('api/private/test', 201, {
|
expectFetch('/api/private/test', 201, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: 'HedgeDoc'
|
body: 'HedgeDoc'
|
||||||
})
|
})
|
||||||
|
@ -95,7 +95,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
|
|
||||||
describe('sendRequest with custom options', () => {
|
describe('sendRequest with custom options', () => {
|
||||||
it('with one option', async () => {
|
it('with one option', async () => {
|
||||||
expectFetch('api/private/test', 201, {
|
expectFetch('/api/private/test', 201, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
cache: 'force-cache'
|
cache: 'force-cache'
|
||||||
})
|
})
|
||||||
|
@ -107,7 +107,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('overriding single option', async () => {
|
it('overriding single option', async () => {
|
||||||
expectFetch('api/private/test', 201, {
|
expectFetch('/api/private/test', 201, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
cache: 'no-store'
|
cache: 'no-store'
|
||||||
})
|
})
|
||||||
|
@ -122,7 +122,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with multiple options', async () => {
|
it('with multiple options', async () => {
|
||||||
expectFetch('api/private/test', 201, {
|
expectFetch('/api/private/test', 201, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
cache: 'force-cache',
|
cache: 'force-cache',
|
||||||
integrity: 'test'
|
integrity: 'test'
|
||||||
|
@ -138,13 +138,13 @@ describe('PostApiRequestBuilder', () => {
|
||||||
|
|
||||||
describe('failing sendRequest', () => {
|
describe('failing sendRequest', () => {
|
||||||
it('without backend provided error name or error message', async () => {
|
it('without backend provided error name or error message', async () => {
|
||||||
expectFetch('api/private/test', 400, { method: 'POST' })
|
expectFetch('/api/private/test', 400, { method: 'POST' })
|
||||||
const request = new PostApiRequestBuilder<string, string>('test').sendRequest()
|
const request = new PostApiRequestBuilder<string, string>('test').sendRequest()
|
||||||
await expect(request).rejects.toEqual(new ApiError(400, undefined, undefined))
|
await expect(request).rejects.toEqual(new ApiError(400, undefined, undefined))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with backend error name and error message', async () => {
|
it('with backend error name and error message', async () => {
|
||||||
expectFetch('api/private/test', 400, { method: 'POST' }, {
|
expectFetch('/api/private/test', 400, { method: 'POST' }, {
|
||||||
message: 'The API has exploded!',
|
message: 'The API has exploded!',
|
||||||
name: 'testExplosion'
|
name: 'testExplosion'
|
||||||
} as ApiErrorResponse)
|
} as ApiErrorResponse)
|
||||||
|
@ -153,7 +153,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with another status code than 400', async () => {
|
it('with another status code than 400', async () => {
|
||||||
expectFetch('api/private/test', 401, { method: 'POST' }, {
|
expectFetch('/api/private/test', 401, { method: 'POST' }, {
|
||||||
message: 'The API has exploded!',
|
message: 'The API has exploded!',
|
||||||
name: 'testExplosion'
|
name: 'testExplosion'
|
||||||
} as ApiErrorResponse)
|
} as ApiErrorResponse)
|
||||||
|
|
|
@ -26,14 +26,14 @@ describe('PutApiRequestBuilder', () => {
|
||||||
|
|
||||||
describe('sendRequest without body', () => {
|
describe('sendRequest without body', () => {
|
||||||
it('without headers', async () => {
|
it('without headers', async () => {
|
||||||
expectFetch('api/private/test', 200, { method: 'PUT' })
|
expectFetch('/api/private/test', 200, { method: 'PUT' })
|
||||||
await new PutApiRequestBuilder<string, undefined>('test').sendRequest()
|
await new PutApiRequestBuilder<string, undefined>('test').sendRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with single header', async () => {
|
it('with single header', async () => {
|
||||||
const expectedHeaders = new Headers()
|
const expectedHeaders = new Headers()
|
||||||
expectedHeaders.append('test', 'true')
|
expectedHeaders.append('test', 'true')
|
||||||
expectFetch('api/private/test', 200, {
|
expectFetch('/api/private/test', 200, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
|
@ -43,7 +43,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
it('with overriding single header', async () => {
|
it('with overriding single header', async () => {
|
||||||
const expectedHeaders = new Headers()
|
const expectedHeaders = new Headers()
|
||||||
expectedHeaders.append('test', 'false')
|
expectedHeaders.append('test', 'false')
|
||||||
expectFetch('api/private/test', 200, {
|
expectFetch('/api/private/test', 200, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
|
@ -57,7 +57,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
const expectedHeaders = new Headers()
|
const expectedHeaders = new Headers()
|
||||||
expectedHeaders.append('test', 'true')
|
expectedHeaders.append('test', 'true')
|
||||||
expectedHeaders.append('test2', 'false')
|
expectedHeaders.append('test2', 'false')
|
||||||
expectFetch('api/private/test', 200, {
|
expectFetch('/api/private/test', 200, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
|
@ -72,7 +72,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
const expectedHeaders = new Headers()
|
const expectedHeaders = new Headers()
|
||||||
expectedHeaders.append('Content-Type', 'application/json')
|
expectedHeaders.append('Content-Type', 'application/json')
|
||||||
|
|
||||||
expectFetch('api/private/test', 200, {
|
expectFetch('/api/private/test', 200, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: expectedHeaders,
|
headers: expectedHeaders,
|
||||||
body: '{"test":true,"foo":"bar"}'
|
body: '{"test":true,"foo":"bar"}'
|
||||||
|
@ -86,7 +86,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sendRequest with other body', async () => {
|
it('sendRequest with other body', async () => {
|
||||||
expectFetch('api/private/test', 200, {
|
expectFetch('/api/private/test', 200, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: 'HedgeDoc'
|
body: 'HedgeDoc'
|
||||||
})
|
})
|
||||||
|
@ -95,7 +95,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
|
|
||||||
describe('sendRequest with custom options', () => {
|
describe('sendRequest with custom options', () => {
|
||||||
it('with one option', async () => {
|
it('with one option', async () => {
|
||||||
expectFetch('api/private/test', 200, {
|
expectFetch('/api/private/test', 200, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
cache: 'force-cache'
|
cache: 'force-cache'
|
||||||
})
|
})
|
||||||
|
@ -107,7 +107,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('overriding single option', async () => {
|
it('overriding single option', async () => {
|
||||||
expectFetch('api/private/test', 200, {
|
expectFetch('/api/private/test', 200, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
cache: 'no-store'
|
cache: 'no-store'
|
||||||
})
|
})
|
||||||
|
@ -122,7 +122,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with multiple options', async () => {
|
it('with multiple options', async () => {
|
||||||
expectFetch('api/private/test', 200, {
|
expectFetch('/api/private/test', 200, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
cache: 'force-cache',
|
cache: 'force-cache',
|
||||||
integrity: 'test'
|
integrity: 'test'
|
||||||
|
@ -138,13 +138,13 @@ describe('PutApiRequestBuilder', () => {
|
||||||
|
|
||||||
describe('failing sendRequest', () => {
|
describe('failing sendRequest', () => {
|
||||||
it('without backend provided error name or error message', async () => {
|
it('without backend provided error name or error message', async () => {
|
||||||
expectFetch('api/private/test', 400, { method: 'PUT' })
|
expectFetch('/api/private/test', 400, { method: 'PUT' })
|
||||||
const request = new PutApiRequestBuilder<string, undefined>('test').sendRequest()
|
const request = new PutApiRequestBuilder<string, undefined>('test').sendRequest()
|
||||||
await expect(request).rejects.toEqual(new ApiError(400, undefined, undefined))
|
await expect(request).rejects.toEqual(new ApiError(400, undefined, undefined))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with backend error name and error message', async () => {
|
it('with backend error name and error message', async () => {
|
||||||
expectFetch('api/private/test', 400, { method: 'PUT' }, {
|
expectFetch('/api/private/test', 400, { method: 'PUT' }, {
|
||||||
message: 'The API has exploded!',
|
message: 'The API has exploded!',
|
||||||
name: 'testExplosion'
|
name: 'testExplosion'
|
||||||
} as ApiErrorResponse)
|
} as ApiErrorResponse)
|
||||||
|
@ -153,7 +153,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with another status code than 400', async () => {
|
it('with another status code than 400', async () => {
|
||||||
expectFetch('api/private/test', 401, { method: 'PUT' }, {
|
expectFetch('/api/private/test', 401, { method: 'PUT' }, {
|
||||||
message: 'The API has exploded!',
|
message: 'The API has exploded!',
|
||||||
name: 'testExplosion'
|
name: 'testExplosion'
|
||||||
} as ApiErrorResponse)
|
} as ApiErrorResponse)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { fetchMotd } from './fetch-motd'
|
||||||
import { Mock } from 'ts-mockery'
|
import { Mock } from 'ts-mockery'
|
||||||
|
|
||||||
describe('fetch motd', () => {
|
describe('fetch motd', () => {
|
||||||
const motdUrl = 'public/motd.md'
|
const motdUrl = '/public/motd.md'
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
window.localStorage.clear()
|
window.localStorage.clear()
|
||||||
|
|
|
@ -23,7 +23,7 @@ export interface MotdApiResponse {
|
||||||
*/
|
*/
|
||||||
export const fetchMotd = async (): Promise<MotdApiResponse | undefined> => {
|
export const fetchMotd = async (): Promise<MotdApiResponse | undefined> => {
|
||||||
const cachedLastModified = window.localStorage.getItem(MOTD_LOCAL_STORAGE_KEY)
|
const cachedLastModified = window.localStorage.getItem(MOTD_LOCAL_STORAGE_KEY)
|
||||||
const motdUrl = `public/motd.md`
|
const motdUrl = `/public/motd.md`
|
||||||
|
|
||||||
if (cachedLastModified) {
|
if (cachedLastModified) {
|
||||||
const response = await fetch(motdUrl, {
|
const response = await fetch(motdUrl, {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { defaultConfig } from '../../api/common/default-config'
|
||||||
* @throws {Error} if the content can't be fetched
|
* @throws {Error} if the content can't be fetched
|
||||||
*/
|
*/
|
||||||
export const fetchFrontPageContent = async (): Promise<string> => {
|
export const fetchFrontPageContent = async (): Promise<string> => {
|
||||||
const response = await fetch('public/intro.md', {
|
const response = await fetch('/public/intro.md', {
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { useAppTitle } from '../../hooks/common/use-app-title'
|
import { useAppTitle } from '../../hooks/common/use-app-title'
|
||||||
import { useBaseUrl } from '../../hooks/common/use-base-url'
|
|
||||||
import { FavIcon } from './fav-icon'
|
import { FavIcon } from './fav-icon'
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
@ -14,12 +13,10 @@ import React from 'react'
|
||||||
*/
|
*/
|
||||||
export const BaseHead: React.FC = () => {
|
export const BaseHead: React.FC = () => {
|
||||||
const appTitle = useAppTitle()
|
const appTitle = useAppTitle()
|
||||||
const baseUrl = useBaseUrl()
|
|
||||||
return (
|
return (
|
||||||
<Head>
|
<Head>
|
||||||
<title>{appTitle}</title>
|
<title>{appTitle}</title>
|
||||||
<FavIcon />
|
<FavIcon />
|
||||||
<base href={baseUrl} />
|
|
||||||
<meta content='width=device-width, initial-scale=1' name='viewport' />
|
<meta content='width=device-width, initial-scale=1' name='viewport' />
|
||||||
</Head>
|
</Head>
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,17 +11,17 @@ import React, { Fragment } from 'react'
|
||||||
export const FavIcon: React.FC = () => {
|
export const FavIcon: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<link href='icons/apple-touch-icon.png' rel='apple-touch-icon' sizes='180x180' />
|
<link href='/icons/apple-touch-icon.png' rel='apple-touch-icon' sizes='180x180' />
|
||||||
<link href='icons/favicon-32x32.png' rel='icon' sizes='32x32' type='image/png' />
|
<link href='/icons/favicon-32x32.png' rel='icon' sizes='32x32' type='image/png' />
|
||||||
<link href='icons/favicon-16x16.png' rel='icon' sizes='16x16' type='image/png' />
|
<link href='/icons/favicon-16x16.png' rel='icon' sizes='16x16' type='image/png' />
|
||||||
<link href='icons/site.webmanifest' rel='manifest' />
|
<link href='/icons/site.webmanifest' rel='manifest' />
|
||||||
<link href='icons/favicon.ico' rel='shortcut icon' />
|
<link href='/icons/favicon.ico' rel='shortcut icon' />
|
||||||
<link color='#b51f08' href='icons/safari-pinned-tab.svg' rel='mask-icon' />
|
<link color='#b51f08' href='/icons/safari-pinned-tab.svg' rel='mask-icon' />
|
||||||
<meta name='apple-mobile-web-app-title' content='HedgeDoc' />
|
<meta name='apple-mobile-web-app-title' content='HedgeDoc' />
|
||||||
<meta name='application-name' content='HedgeDoc' />
|
<meta name='application-name' content='HedgeDoc' />
|
||||||
<meta name='msapplication-TileColor' content='#b51f08' />
|
<meta name='msapplication-TileColor' content='#b51f08' />
|
||||||
<meta name='theme-color' content='#b51f08' />
|
<meta name='theme-color' content='#b51f08' />
|
||||||
<meta content='icons/browserconfig.xml' name='msapplication-config' />
|
<meta content='/icons/browserconfig.xml' name='msapplication-config' />
|
||||||
<meta content='HedgeDoc - Collaborative markdown notes' name='description' />
|
<meta content='HedgeDoc - Collaborative markdown notes' name='description' />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
|
|
|
@ -54,7 +54,7 @@ const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
],
|
],
|
||||||
branding: {
|
branding: {
|
||||||
name: 'DEMO Corp',
|
name: 'DEMO Corp',
|
||||||
logo: 'public/img/demo.png'
|
logo: '/public/img/demo.png'
|
||||||
},
|
},
|
||||||
useImageProxy: false,
|
useImageProxy: false,
|
||||||
specialUrls: {
|
specialUrls: {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
|
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
|
||||||
respondToMatchingRequest<LoginUserInfo>(HttpMethod.GET, req, res, {
|
respondToMatchingRequest<LoginUserInfo>(HttpMethod.GET, req, res, {
|
||||||
username: 'mock',
|
username: 'mock',
|
||||||
photo: 'public/img/avatar.png',
|
photo: '/public/img/avatar.png',
|
||||||
displayName: 'Mock User',
|
displayName: 'Mock User',
|
||||||
authProvider: 'local',
|
authProvider: 'local',
|
||||||
email: 'mock@hedgedoc.test'
|
email: 'mock@hedgedoc.test'
|
||||||
|
|
|
@ -20,7 +20,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse): Promise<void>
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
{
|
{
|
||||||
url: 'public/img/avatar.png',
|
url: '/public/img/avatar.png',
|
||||||
noteId: null,
|
noteId: null,
|
||||||
username: 'test',
|
username: 'test',
|
||||||
createdAt: '2022-02-27T21:54:23.856Z'
|
createdAt: '2022-02-27T21:54:23.856Z'
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -11,7 +11,7 @@ const handler = (req: NextApiRequest, res: NextApiResponse): void => {
|
||||||
respondToMatchingRequest<UserInfo>(HttpMethod.GET, req, res, {
|
respondToMatchingRequest<UserInfo>(HttpMethod.GET, req, res, {
|
||||||
username: 'erik',
|
username: 'erik',
|
||||||
displayName: 'Erik',
|
displayName: 'Erik',
|
||||||
photo: 'public/img/avatar.png'
|
photo: '/public/img/avatar.png'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ const handler = (req: NextApiRequest, res: NextApiResponse): void => {
|
||||||
respondToMatchingRequest<UserInfo>(HttpMethod.GET, req, res, {
|
respondToMatchingRequest<UserInfo>(HttpMethod.GET, req, res, {
|
||||||
username: 'molly',
|
username: 'molly',
|
||||||
displayName: 'Molly',
|
displayName: 'Molly',
|
||||||
photo: 'public/img/avatar.png'
|
photo: '/public/img/avatar.png'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ const handler = (req: NextApiRequest, res: NextApiResponse): void => {
|
||||||
respondToMatchingRequest<UserInfo>(HttpMethod.GET, req, res, {
|
respondToMatchingRequest<UserInfo>(HttpMethod.GET, req, res, {
|
||||||
username: 'tilman',
|
username: 'tilman',
|
||||||
displayName: 'Tilman',
|
displayName: 'Tilman',
|
||||||
photo: 'public/img/avatar.png'
|
photo: '/public/img/avatar.png'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import type { BaseUrls } from '../components/common/base-url/base-url-context-provider'
|
import type { BaseUrls } from '../components/common/base-url/base-url-context-provider'
|
||||||
import { Logger } from './logger'
|
import { Logger } from './logger'
|
||||||
import { isTestMode } from './test-modes'
|
import { isTestMode } from './test-modes'
|
||||||
import { MissingTrailingSlashError, parseUrl } from '@hedgedoc/commons'
|
import { NoSubdirectoryAllowedError, parseUrl } from '@hedgedoc/commons'
|
||||||
import { Optional } from '@mrdrogdrog/optional'
|
import { Optional } from '@mrdrogdrog/optional'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,8 +20,8 @@ export class BaseUrlFromEnvExtractor {
|
||||||
try {
|
try {
|
||||||
return parseUrl(envVarValue)
|
return parseUrl(envVarValue)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof MissingTrailingSlashError) {
|
if (error instanceof NoSubdirectoryAllowedError) {
|
||||||
this.logger.error(`The path in ${envVarName} must end with an '/'`)
|
this.logger.error(error.message)
|
||||||
return Optional.empty()
|
return Optional.empty()
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|
Loading…
Reference in a new issue