feat: add fluxer upstream source and self-hosting documentation
- Clone of github.com/fluxerapp/fluxer (official upstream) - SELF_HOSTING.md: full VM rebuild procedure, architecture overview, service reference, step-by-step setup, troubleshooting, seattle reference - dev/.env.example: all env vars with secrets redacted and generation instructions - dev/livekit.yaml: LiveKit config template with placeholder keys - fluxer-seattle/: existing seattle deployment setup scripts
This commit is contained in:
50
fluxer/packages/api/src/theme/ThemeController.tsx
Normal file
50
fluxer/packages/api/src/theme/ThemeController.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {DefaultUserOnly, LoginRequired} from '@fluxer/api/src/middleware/AuthMiddleware';
|
||||
import {RateLimitMiddleware} from '@fluxer/api/src/middleware/RateLimitMiddleware';
|
||||
import {OpenAPI} from '@fluxer/api/src/middleware/ResponseTypeMiddleware';
|
||||
import {RateLimitConfigs} from '@fluxer/api/src/RateLimitConfig';
|
||||
import type {HonoApp} from '@fluxer/api/src/types/HonoEnv';
|
||||
import {Validator} from '@fluxer/api/src/Validator';
|
||||
import {ThemeCreateRequest, ThemeCreateResponse} from '@fluxer/schema/src/domains/theme/ThemeSchemas';
|
||||
|
||||
export function ThemeController(app: HonoApp) {
|
||||
app.post(
|
||||
'/users/@me/themes',
|
||||
RateLimitMiddleware(RateLimitConfigs.THEME_SHARE_CREATE),
|
||||
LoginRequired,
|
||||
DefaultUserOnly,
|
||||
OpenAPI({
|
||||
operationId: 'create_theme',
|
||||
summary: 'Create theme',
|
||||
responseSchema: ThemeCreateResponse,
|
||||
statusCode: 201,
|
||||
security: ['botToken', 'bearerToken', 'sessionToken'],
|
||||
tags: ['Themes'],
|
||||
description: 'Creates a new custom theme with CSS styling that can be shared with other users.',
|
||||
}),
|
||||
Validator('json', ThemeCreateRequest),
|
||||
async (ctx) => {
|
||||
const {css} = ctx.req.valid('json');
|
||||
const theme = await ctx.get('themeService').createTheme(css);
|
||||
return ctx.json(theme, 201);
|
||||
},
|
||||
);
|
||||
}
|
||||
55
fluxer/packages/api/src/theme/ThemeService.tsx
Normal file
55
fluxer/packages/api/src/theme/ThemeService.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {randomBytes} from 'node:crypto';
|
||||
import {Config} from '@fluxer/api/src/Config';
|
||||
import type {IStorageService} from '@fluxer/api/src/infrastructure/IStorageService';
|
||||
import {getMetricsService} from '@fluxer/api/src/infrastructure/MetricsService';
|
||||
import {FileSizeTooLargeError} from '@fluxer/errors/src/domains/core/FileSizeTooLargeError';
|
||||
|
||||
const MAX_CSS_BYTES = 8 * 1024 * 1024;
|
||||
|
||||
export class ThemeService {
|
||||
constructor(private readonly storageService: IStorageService) {}
|
||||
|
||||
async createTheme(css: string): Promise<{id: string}> {
|
||||
const cssBytes = Buffer.from(css, 'utf-8');
|
||||
|
||||
if (cssBytes.length > MAX_CSS_BYTES) {
|
||||
throw new FileSizeTooLargeError();
|
||||
}
|
||||
|
||||
const themeId = randomBytes(8).toString('hex');
|
||||
await this.storageService.uploadObject({
|
||||
bucket: Config.s3.buckets.cdn,
|
||||
key: `themes/${themeId}.css`,
|
||||
body: cssBytes,
|
||||
contentType: 'text/css; charset=utf-8',
|
||||
});
|
||||
|
||||
getMetricsService().counter({
|
||||
name: 'fluxer.themes.custom_theme_created',
|
||||
dimensions: {
|
||||
css_size_bytes: cssBytes.length.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
return {id: themeId};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {createFakeAuthToken} from '@fluxer/api/src/auth/tests/AuthTestUtils';
|
||||
import {type ApiTestHarness, createApiTestHarness} from '@fluxer/api/src/test/ApiTestHarness';
|
||||
import {HTTP_STATUS} from '@fluxer/api/src/test/TestConstants';
|
||||
import {createBuilder, createBuilderWithoutAuth} from '@fluxer/api/src/test/TestRequestBuilder';
|
||||
import {afterAll, beforeAll, beforeEach, describe, it} from 'vitest';
|
||||
|
||||
describe('Theme authentication required', () => {
|
||||
let harness: ApiTestHarness;
|
||||
|
||||
beforeAll(async () => {
|
||||
harness = await createApiTestHarness();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await harness.reset();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await harness?.shutdown();
|
||||
});
|
||||
|
||||
it('rejects request without authentication', async () => {
|
||||
await createBuilderWithoutAuth(harness)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: '.test { color: red; }'})
|
||||
.expect(HTTP_STATUS.UNAUTHORIZED, 'UNAUTHORIZED')
|
||||
.execute();
|
||||
});
|
||||
|
||||
it('rejects request with invalid token', async () => {
|
||||
const fakeToken = createFakeAuthToken();
|
||||
|
||||
await createBuilder(harness, fakeToken)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: '.test { color: red; }'})
|
||||
.expect(HTTP_STATUS.UNAUTHORIZED, 'UNAUTHORIZED')
|
||||
.execute();
|
||||
});
|
||||
|
||||
it('rejects request with empty authorization header', async () => {
|
||||
await createBuilder(harness, '')
|
||||
.post('/users/@me/themes')
|
||||
.body({css: '.test { color: red; }'})
|
||||
.expect(HTTP_STATUS.UNAUTHORIZED, 'UNAUTHORIZED')
|
||||
.execute();
|
||||
});
|
||||
|
||||
it('rejects request with malformed token', async () => {
|
||||
await createBuilder(harness, 'not-a-valid-token')
|
||||
.post('/users/@me/themes')
|
||||
.body({css: '.test { color: red; }'})
|
||||
.expect(HTTP_STATUS.UNAUTHORIZED, 'UNAUTHORIZED')
|
||||
.execute();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {createTestBotAccount} from '@fluxer/api/src/bot/tests/BotTestUtils';
|
||||
import {type ApiTestHarness, createApiTestHarness} from '@fluxer/api/src/test/ApiTestHarness';
|
||||
import {HTTP_STATUS} from '@fluxer/api/src/test/TestConstants';
|
||||
import {createBuilder} from '@fluxer/api/src/test/TestRequestBuilder';
|
||||
import {afterAll, beforeAll, beforeEach, describe, it} from 'vitest';
|
||||
|
||||
describe('Theme bot user denied', () => {
|
||||
let harness: ApiTestHarness;
|
||||
|
||||
beforeAll(async () => {
|
||||
harness = await createApiTestHarness();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await harness.reset();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await harness?.shutdown();
|
||||
});
|
||||
|
||||
it('rejects theme creation from bot users', async () => {
|
||||
const botAccount = await createTestBotAccount(harness);
|
||||
|
||||
await createBuilder(harness, `Bot ${botAccount.botToken}`)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: '.test { color: red; }'})
|
||||
.expect(HTTP_STATUS.FORBIDDEN, 'ACCESS_DENIED')
|
||||
.execute();
|
||||
});
|
||||
});
|
||||
116
fluxer/packages/api/src/theme/tests/ThemeCreation.test.tsx
Normal file
116
fluxer/packages/api/src/theme/tests/ThemeCreation.test.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {createTestAccount} from '@fluxer/api/src/auth/tests/AuthTestUtils';
|
||||
import {type ApiTestHarness, createApiTestHarness} from '@fluxer/api/src/test/ApiTestHarness';
|
||||
import {HTTP_STATUS} from '@fluxer/api/src/test/TestConstants';
|
||||
import {createBuilder} from '@fluxer/api/src/test/TestRequestBuilder';
|
||||
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';
|
||||
|
||||
interface ThemeCreateResponse {
|
||||
id: string;
|
||||
}
|
||||
|
||||
describe('Theme creation', () => {
|
||||
let harness: ApiTestHarness;
|
||||
|
||||
beforeAll(async () => {
|
||||
harness = await createApiTestHarness();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await harness.reset();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await harness?.shutdown();
|
||||
});
|
||||
|
||||
it('successfully creates a theme with valid CSS', async () => {
|
||||
const user = await createTestAccount(harness);
|
||||
|
||||
const theme = await createBuilder<ThemeCreateResponse>(harness, user.token)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: '.test { color: red; }'})
|
||||
.expect(HTTP_STATUS.CREATED)
|
||||
.execute();
|
||||
|
||||
expect(theme.id).toBeDefined();
|
||||
expect(typeof theme.id).toBe('string');
|
||||
expect(theme.id.length).toBe(16);
|
||||
});
|
||||
|
||||
it('successfully creates a theme with complex CSS', async () => {
|
||||
const user = await createTestAccount(harness);
|
||||
|
||||
const complexCss = `
|
||||
:root {
|
||||
--background-primary: #36393f;
|
||||
--background-secondary: #2f3136;
|
||||
--text-normal: #dcddde;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const theme = await createBuilder<ThemeCreateResponse>(harness, user.token)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: complexCss})
|
||||
.expect(HTTP_STATUS.CREATED)
|
||||
.execute();
|
||||
|
||||
expect(theme.id).toBeDefined();
|
||||
});
|
||||
|
||||
it('successfully creates a theme with minimal CSS (1 character)', async () => {
|
||||
const user = await createTestAccount(harness);
|
||||
|
||||
const theme = await createBuilder<ThemeCreateResponse>(harness, user.token)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: 'a'})
|
||||
.expect(HTTP_STATUS.CREATED)
|
||||
.execute();
|
||||
|
||||
expect(theme.id).toBeDefined();
|
||||
});
|
||||
|
||||
it('successfully creates a theme with unicode CSS content', async () => {
|
||||
const user = await createTestAccount(harness);
|
||||
|
||||
const unicodeCss = '.test::before { content: "\u2764\ufe0f"; }';
|
||||
|
||||
const theme = await createBuilder<ThemeCreateResponse>(harness, user.token)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: unicodeCss})
|
||||
.expect(HTTP_STATUS.CREATED)
|
||||
.execute();
|
||||
|
||||
expect(theme.id).toBeDefined();
|
||||
});
|
||||
});
|
||||
101
fluxer/packages/api/src/theme/tests/ThemeCssSizeLimit.test.tsx
Normal file
101
fluxer/packages/api/src/theme/tests/ThemeCssSizeLimit.test.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {createTestAccount} from '@fluxer/api/src/auth/tests/AuthTestUtils';
|
||||
import {type ApiTestHarness, createApiTestHarness} from '@fluxer/api/src/test/ApiTestHarness';
|
||||
import {HTTP_STATUS} from '@fluxer/api/src/test/TestConstants';
|
||||
import {createBuilder} from '@fluxer/api/src/test/TestRequestBuilder';
|
||||
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';
|
||||
|
||||
interface ThemeCreateResponse {
|
||||
id: string;
|
||||
}
|
||||
|
||||
const MAX_CSS_BYTES = 8 * 1024 * 1024;
|
||||
|
||||
describe('Theme CSS size limits', () => {
|
||||
let harness: ApiTestHarness;
|
||||
|
||||
beforeAll(async () => {
|
||||
harness = await createApiTestHarness();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await harness.reset();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await harness?.shutdown();
|
||||
});
|
||||
|
||||
it('rejects CSS that exceeds the 8MB limit', async () => {
|
||||
const user = await createTestAccount(harness);
|
||||
|
||||
const oversizedCss = 'a'.repeat(MAX_CSS_BYTES + 1);
|
||||
|
||||
await createBuilder(harness, user.token)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: oversizedCss})
|
||||
.expect(HTTP_STATUS.BAD_REQUEST, 'FILE_SIZE_TOO_LARGE')
|
||||
.execute();
|
||||
});
|
||||
|
||||
it('accepts CSS at exactly the 8MB limit', async () => {
|
||||
const user = await createTestAccount(harness);
|
||||
|
||||
const maxCss = 'a'.repeat(MAX_CSS_BYTES);
|
||||
|
||||
const theme = await createBuilder<ThemeCreateResponse>(harness, user.token)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: maxCss})
|
||||
.expect(HTTP_STATUS.CREATED)
|
||||
.execute();
|
||||
|
||||
expect(theme.id).toBeDefined();
|
||||
});
|
||||
|
||||
it('accepts CSS just under the 8MB limit', async () => {
|
||||
const user = await createTestAccount(harness);
|
||||
|
||||
const nearMaxCss = 'a'.repeat(MAX_CSS_BYTES - 1);
|
||||
|
||||
const theme = await createBuilder<ThemeCreateResponse>(harness, user.token)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: nearMaxCss})
|
||||
.expect(HTTP_STATUS.CREATED)
|
||||
.execute();
|
||||
|
||||
expect(theme.id).toBeDefined();
|
||||
});
|
||||
|
||||
it('rejects CSS exceeding limit with multibyte unicode characters', async () => {
|
||||
const user = await createTestAccount(harness);
|
||||
|
||||
const unicodeChar = '\u{1F600}';
|
||||
const bytesPerChar = Buffer.from(unicodeChar, 'utf-8').length;
|
||||
const charsNeeded = Math.ceil((MAX_CSS_BYTES + 1) / bytesPerChar);
|
||||
const oversizedUnicodeCss = unicodeChar.repeat(charsNeeded);
|
||||
|
||||
await createBuilder(harness, user.token)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: oversizedUnicodeCss})
|
||||
.expect(HTTP_STATUS.BAD_REQUEST, 'FILE_SIZE_TOO_LARGE')
|
||||
.execute();
|
||||
});
|
||||
});
|
||||
110
fluxer/packages/api/src/theme/tests/ThemeValidation.test.tsx
Normal file
110
fluxer/packages/api/src/theme/tests/ThemeValidation.test.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {createTestAccount} from '@fluxer/api/src/auth/tests/AuthTestUtils';
|
||||
import {type ApiTestHarness, createApiTestHarness} from '@fluxer/api/src/test/ApiTestHarness';
|
||||
import {HTTP_STATUS} from '@fluxer/api/src/test/TestConstants';
|
||||
import {createBuilder} from '@fluxer/api/src/test/TestRequestBuilder';
|
||||
import {afterAll, beforeAll, beforeEach, describe, it} from 'vitest';
|
||||
|
||||
describe('Theme validation', () => {
|
||||
let harness: ApiTestHarness;
|
||||
|
||||
beforeAll(async () => {
|
||||
harness = await createApiTestHarness();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await harness.reset();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await harness?.shutdown();
|
||||
});
|
||||
|
||||
it('rejects request with missing css field', async () => {
|
||||
const user = await createTestAccount(harness);
|
||||
|
||||
await createBuilder(harness, user.token)
|
||||
.post('/users/@me/themes')
|
||||
.body({})
|
||||
.expect(HTTP_STATUS.BAD_REQUEST)
|
||||
.execute();
|
||||
});
|
||||
|
||||
it('rejects request with empty css string', async () => {
|
||||
const user = await createTestAccount(harness);
|
||||
|
||||
await createBuilder(harness, user.token)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: ''})
|
||||
.expect(HTTP_STATUS.BAD_REQUEST)
|
||||
.execute();
|
||||
});
|
||||
|
||||
it('rejects request with null css value', async () => {
|
||||
const user = await createTestAccount(harness);
|
||||
|
||||
await createBuilder(harness, user.token)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: null})
|
||||
.expect(HTTP_STATUS.BAD_REQUEST)
|
||||
.execute();
|
||||
});
|
||||
|
||||
it('rejects request with numeric css value', async () => {
|
||||
const user = await createTestAccount(harness);
|
||||
|
||||
await createBuilder(harness, user.token)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: 12345})
|
||||
.expect(HTTP_STATUS.BAD_REQUEST)
|
||||
.execute();
|
||||
});
|
||||
|
||||
it('rejects request with array css value', async () => {
|
||||
const user = await createTestAccount(harness);
|
||||
|
||||
await createBuilder(harness, user.token)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: ['body { color: red; }']})
|
||||
.expect(HTTP_STATUS.BAD_REQUEST)
|
||||
.execute();
|
||||
});
|
||||
|
||||
it('rejects request with object css value', async () => {
|
||||
const user = await createTestAccount(harness);
|
||||
|
||||
await createBuilder(harness, user.token)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: {content: 'body { color: red; }'}})
|
||||
.expect(HTTP_STATUS.BAD_REQUEST)
|
||||
.execute();
|
||||
});
|
||||
|
||||
it('rejects request with boolean css value', async () => {
|
||||
const user = await createTestAccount(harness);
|
||||
|
||||
await createBuilder(harness, user.token)
|
||||
.post('/users/@me/themes')
|
||||
.body({css: true})
|
||||
.expect(HTTP_STATUS.BAD_REQUEST)
|
||||
.execute();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user