fix(backend): type and linting fixes found by oxlint type-aware check

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson
2026-05-07 00:28:45 +02:00
committed by Philip Molares
parent 420925f490
commit ea9e0bdbd1
75 changed files with 372 additions and 279 deletions
+5 -3
View File
@@ -11,7 +11,6 @@
"hedgedoc-local/correct-logger-context": "error",
"typescript/no-unused-vars": ["warn", { "argsIgnorePattern": "^_+$" }],
"typescript/return-await": ["error", "always"],
"typescript/naming-convention": "off",
"typescript/no-base-to-string": "error"
},
"overrides": [
@@ -24,19 +23,22 @@
"typescript/no-unsafe-member-access": "off",
"typescript/require-await": "off",
"typescript/explicit-function-return-type": "off",
"jest/unbound-method": "error",
"jest/no-standalone-expect": "off"
}
},
{
"files": ["src/database/**"],
"rules": {
"typescript/naming-convention": "off"
}
}
],
"env": {
"node": true,
"jest": true
},
"options": {
"typeAware": true,
"typeCheck": true,
"maxWarnings": 0
}
}
+1 -1
View File
@@ -76,6 +76,7 @@
"zod": "3.25.76"
},
"devDependencies": {
"@jest/types": "30.3.0",
"@nestjs/cli": "10.4.9",
"@nestjs/schematics": "10.2.3",
"@nestjs/testing": "10.4.22",
@@ -84,7 +85,6 @@
"@types/cli-color": "2.0.6",
"@types/cookie": "1.0.0",
"@types/cookie-signature": "1.1.2",
"@types/jest": "29.5.14",
"@types/luxon": "3.7.1",
"@types/markdown-it": "13.0.8",
"@types/node": "24.10.7",
+2 -1
View File
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeAll, beforeEach, afterEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import { FieldNameAlias, TableAlias } from '@hedgedoc/database';
import { Provider } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
@@ -27,7 +29,6 @@ import { LoggerModule } from '../logger/logger.module';
import { AliasService } from './alias.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { NoteEventMap } from '../events';
import SpyInstance = jest.SpyInstance;
describe('AliasService', () => {
const alias1 = 'testAlias1';
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeAll, beforeEach, afterEach, jest } from '@jest/globals';
import { FieldNameApiToken, TableApiToken } from '@hedgedoc/database';
import { Provider } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@@ -47,7 +47,7 @@ export class AuthController {
if (request.session.loginAuthProviderType === AuthProviderType.OIDC) {
logoutUrl = this.oidcService.getLogoutUrl(request);
}
const destroySessionPromise = promisify(request.session.destroy).bind(request.session);
const destroySessionPromise = promisify(request.session.destroy.bind(request.session));
try {
await destroySessionPromise();
return LogoutResponseDto.create({
+1 -1
View File
@@ -45,7 +45,7 @@ export class CsrfGuard implements CanActivate {
throw new ForbiddenException('CSRF token required');
}
const csrfProtection = request.server.csrfProtection;
const csrfProtection = request.server.csrfProtection.bind(request.server);
if (!csrfProtection) {
throw new ForbiddenException('CSRF protection failed to load');
}
+2 -2
View File
@@ -71,8 +71,8 @@ export class MeController {
this.logger.debug(`Deleted all media uploads for user with id ${userId}`);
await this.userService.deleteUser(userId);
this.logger.debug(`Deleted user with id ${userId}`);
const destroySessionPromise = promisify(request.session.destroy).bind(request.session);
destroySessionPromise().catch((error) => {
const destroySessionPromise = promisify(request.session.destroy.bind(request.session));
destroySessionPromise().catch((error: Error) => {
this.logger.error('Error while destroying session:' + String(error), undefined, 'deleteUser');
throw new InternalServerErrorException('Error trying to destroy session of deleted user');
});
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach } from '@jest/globals';
import { Note } from '@hedgedoc/database';
import { Mock } from 'ts-mockery';
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import { Note } from '@hedgedoc/database';
import { CallHandler, ExecutionContext } from '@nestjs/common';
import { HttpArgumentsHost } from '@nestjs/common/interfaces/features/arguments-host.interface';
@@ -22,7 +24,7 @@ describe('get note interceptor', () => {
});
let notesService: NoteService;
let noteFetchSpy: jest.SpyInstance;
let noteFetchSpy: SpyInstance<typeof notesService.getNoteIdByAlias>;
beforeEach(() => {
notesService = Mock.of<NoteService>({
+1 -1
View File
@@ -6,7 +6,7 @@
import { AuthProviderType } from '@hedgedoc/commons';
import { FieldNameNote, FieldNameUser, Note, User } from '@hedgedoc/database';
import { FastifyRequest } from 'fastify';
import { SessionState } from 'src/sessions/session-state';
import { SessionState } from '../../sessions/session-state';
export type CompleteRequest = FastifyRequest & {
userId?: User[FieldNameUser.id];
+3 -2
View File
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import { AuthProviderType } from '@hedgedoc/commons';
import { FieldNameIdentity, Identity } from '@hedgedoc/database';
import { BadRequestException, NotFoundException } from '@nestjs/common';
@@ -71,8 +72,8 @@ describe('OidcService', () => {
{
provide: SessionService,
useValue: Mock.of<SessionService>({
terminateSessionByOidcSid: jest.fn(),
terminateAllSessionsOfUser: jest.fn(),
terminateSessionByOidcSid: jest.fn<typeof sessionService.terminateSessionByOidcSid>(),
terminateAllSessionsOfUser: jest.fn<typeof sessionService.terminateAllSessionsOfUser>(),
}),
},
{
+3 -1
View File
@@ -311,7 +311,9 @@ export class OidcService {
field: string,
defaultValue: T,
): string | T {
return response[field] ? String(response[field]) : defaultValue;
return field in response && response[field] !== undefined && response[field] !== null
? (response[field] as string | T)
: defaultValue;
}
/**
+3 -1
View File
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import mockedEnv from 'mocked-env';
import appConfig from './app.config';
@@ -183,7 +185,7 @@ describe('appConfig', () => {
});
describe('throws error', () => {
let spyConsoleError: jest.SpyInstance;
let spyConsoleError: SpyInstance;
let spyProcessExit: jest.Mock;
let originalProcess: typeof process;
+8 -88
View File
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import mockedEnv from 'mocked-env';
import * as utilsModule from './utils';
@@ -14,9 +16,7 @@ import { TEST_CERT_FILE_CONTENT, TEST_CERT_FILE_PATH } from './shared-test-data'
describe('authConfig', () => {
const secret = 'this-is-a-long-but-insecure-secret';
const neededAuthConfig = {
/* oxlint-disable @typescript-eslint/naming-convention */
HD_AUTH_SESSION_SECRET: secret,
/* oxlint-enable @typescript-eslint/naming-convention */
};
describe('local', () => {
@@ -24,20 +24,16 @@ describe('authConfig', () => {
const enableRegister = true;
const minimalPasswordStrength = 1;
const completeLocalConfig = {
/* oxlint-disable @typescript-eslint/naming-convention */
HD_AUTH_LOCAL_ENABLE_LOGIN: String(enableLogin),
HD_AUTH_LOCAL_ENABLE_REGISTER: String(enableRegister),
HD_AUTH_LOCAL_MINIMAL_PASSWORD_STRENGTH: String(minimalPasswordStrength),
/* oxlint-enable @typescript-eslint/naming-convention */
};
describe('is correctly parsed', () => {
it('when given correct and complete environment variables', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLocalConfig,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -53,11 +49,9 @@ describe('authConfig', () => {
it('when HD_AUTH_LOCAL_ENABLE_LOGIN is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLocalConfig,
HD_AUTH_LOCAL_ENABLE_LOGIN: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -73,11 +67,9 @@ describe('authConfig', () => {
it('when HD_AUTH_LOCAL_ENABLE_REGISTER is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLocalConfig,
HD_AUTH_LOCAL_ENABLE_REGISTER: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -93,11 +85,9 @@ describe('authConfig', () => {
it('when HD_AUTH_LOCAL_MINIMAL_PASSWORD_STRENGTH is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLocalConfig,
HD_AUTH_LOCAL_MINIMAL_PASSWORD_STRENGTH: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -112,7 +102,7 @@ describe('authConfig', () => {
});
describe('fails to be parsed', () => {
let spyConsoleError: jest.SpyInstance;
let spyConsoleError: SpyInstance;
let spyProcessExit: jest.Mock;
let originalProcess: typeof process;
@@ -134,11 +124,9 @@ describe('authConfig', () => {
it('when HD_AUTH_LOCAL_MINIMAL_PASSWORD_STRENGTH is 5', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLocalConfig,
HD_AUTH_LOCAL_MINIMAL_PASSWORD_STRENGTH: '5',
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -154,11 +142,9 @@ describe('authConfig', () => {
it('when HD_AUTH_LOCAL_MINIMAL_PASSWORD_STRENGTH is -1', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLocalConfig,
HD_AUTH_LOCAL_MINIMAL_PASSWORD_STRENGTH: '-1',
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -180,9 +166,7 @@ describe('authConfig', () => {
const longSecret = 'a'.repeat(40);
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
HD_AUTH_SESSION_SECRET: longSecret,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -197,9 +181,7 @@ describe('authConfig', () => {
const exactSecret = 'a'.repeat(32);
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
HD_AUTH_SESSION_SECRET: exactSecret,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -212,7 +194,7 @@ describe('authConfig', () => {
});
describe('fails to be parsed', () => {
let spyConsoleError: jest.SpyInstance;
let spyConsoleError: SpyInstance;
let spyProcessExit: jest.Mock;
let originalProcess: typeof process;
@@ -248,9 +230,7 @@ describe('authConfig', () => {
const shortSecret = 'a'.repeat(31);
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
HD_AUTH_SESSION_SECRET: shortSecret,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -290,7 +270,6 @@ describe('authConfig', () => {
const tlsCa = [TEST_CERT_FILE_PATH];
const tlsCaContent = [TEST_CERT_FILE_CONTENT];
const completeLdapConfig = {
/* oxlint-disable @typescript-eslint/naming-convention */
HD_AUTH_LDAP_SERVERS: ldapNames.join(','),
HD_AUTH_LDAP_FUTURAMA_PROVIDER_NAME: providerName,
HD_AUTH_LDAP_FUTURAMA_URL: url,
@@ -304,16 +283,13 @@ describe('authConfig', () => {
HD_AUTH_LDAP_FUTURAMA_BIND_DN: bindDn,
HD_AUTH_LDAP_FUTURAMA_BIND_CREDENTIALS: bindCredentials,
HD_AUTH_LDAP_FUTURAMA_TLS_CERT_PATHS: tlsCa.join(','),
/* oxlint-enable @typescript-eslint/naming-convention */
};
describe('is correctly parsed', () => {
it('when given correct and complete environment variables', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLdapConfig,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -342,11 +318,9 @@ describe('authConfig', () => {
it('when no HD_AUTH_LDAP_FUTURAMA_PROVIDER_NAME is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLdapConfig,
HD_AUTH_LDAP_FUTURAMA_PROVIDER_NAME: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -374,11 +348,9 @@ describe('authConfig', () => {
it('when no HD_AUTH_LDAP_FUTURAMA_SEARCH_FILTER is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLdapConfig,
HD_AUTH_LDAP_FUTURAMA_SEARCH_FILTER: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -406,11 +378,9 @@ describe('authConfig', () => {
it('when no HD_AUTH_LDAP_FUTURAMA_USER_ID_FIELD is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLdapConfig,
HD_AUTH_LDAP_FUTURAMA_USER_ID_FIELD: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -438,11 +408,9 @@ describe('authConfig', () => {
it('when no HD_AUTH_LDAP_FUTURAMA_DISPLAY_NAME_FIELD is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLdapConfig,
HD_AUTH_LDAP_FUTURAMA_DISPLAY_NAME_FIELD: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -470,11 +438,9 @@ describe('authConfig', () => {
it('when no HD_AUTH_LDAP_FUTURAMA_PROFILE_PICTURE_FIELD is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLdapConfig,
HD_AUTH_LDAP_FUTURAMA_PROFILE_PICTURE_FIELD: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -502,11 +468,9 @@ describe('authConfig', () => {
it('when no HD_AUTH_LDAP_FUTURAMA_BIND_DN is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLdapConfig,
HD_AUTH_LDAP_FUTURAMA_BIND_DN: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -534,11 +498,9 @@ describe('authConfig', () => {
it('when no HD_AUTH_LDAP_FUTURAMA_BIND_CREDENTIALS is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLdapConfig,
HD_AUTH_LDAP_FUTURAMA_BIND_CREDENTIALS: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -566,11 +528,9 @@ describe('authConfig', () => {
it('when no HD_AUTH_LDAP_FUTURAMA_TLS_CERT_PATHS is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLdapConfig,
HD_AUTH_LDAP_FUTURAMA_TLS_CERT_PATHS: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -596,12 +556,12 @@ describe('authConfig', () => {
});
});
describe('throws error', () => {
let spyConsoleError: jest.SpyInstance;
let spyConsoleError: SpyInstance;
let spyProcessExit: jest.Mock;
let originalProcess: typeof process;
beforeEach(() => {
spyConsoleError = jest.spyOn(console, 'error').mockImplementation();
spyConsoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
spyProcessExit = jest.fn();
originalProcess = global.process;
global.process = {
@@ -618,11 +578,9 @@ describe('authConfig', () => {
it('when HD_AUTH_LDAP_FUTURAMA_URL is wrong', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLdapConfig,
HD_AUTH_LDAP_FUTURAMA_URL: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -636,11 +594,9 @@ describe('authConfig', () => {
it('when HD_AUTH_LDAP_FUTURAMA_SEARCH_BASE is wrong', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLdapConfig,
HD_AUTH_LDAP_FUTURAMA_SEARCH_BASE: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -656,11 +612,9 @@ describe('authConfig', () => {
it('when HD_AUTH_LDAP_FUTURAMA_TLS_CERT_PATHS is wrong', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeLdapConfig,
HD_AUTH_LDAP_FUTURAMA_TLS_CERT_PATHS: 'not-a-file.pem',
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -700,7 +654,6 @@ describe('authConfig', () => {
const defaultEmailField = 'email';
const enableRegistration = 'false';
const completeOidcConfig = {
/* oxlint-disable @typescript-eslint/naming-convention */
HD_AUTH_OIDC_SERVERS: oidcNames.join(','),
HD_AUTH_OIDC_GITLAB_PROVIDER_NAME: providerName,
HD_AUTH_OIDC_GITLAB_ISSUER: issuer,
@@ -718,16 +671,13 @@ describe('authConfig', () => {
HD_AUTH_OIDC_GITLAB_PROFILE_PICTURE_FIELD: profilePictureField,
HD_AUTH_OIDC_GITLAB_EMAIL_FIELD: emailField,
HD_AUTH_OIDC_GITLAB_ENABLE_REGISTRATION: enableRegistration,
/* oxlint-enable @typescript-eslint/naming-convention */
};
describe('is correctly parsed', () => {
it('when given correct and complete environment variables', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -758,11 +708,9 @@ describe('authConfig', () => {
it('when HD_AUTH_OIDC_GITLAB_THEME is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_THEME: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -793,11 +741,9 @@ describe('authConfig', () => {
it('when HD_AUTH_OIDC_GITLAB_AUTHORIZE_URL is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_AUTHORIZE_URL: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -828,11 +774,9 @@ describe('authConfig', () => {
it('when HD_AUTH_OIDC_GITLAB_TOKEN_URL is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_TOKEN_URL: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -863,11 +807,9 @@ describe('authConfig', () => {
it('when HD_AUTH_OIDC_GITLAB_USERINFO_URL is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_USERINFO_URL: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -898,11 +840,9 @@ describe('authConfig', () => {
it('when HD_AUTH_OIDC_GITLAB_END_SESSION_URL is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_END_SESSION_URL: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -933,11 +873,9 @@ describe('authConfig', () => {
it('when HD_AUTH_OIDC_GITLAB_SCOPE is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_SCOPE: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -968,11 +906,9 @@ describe('authConfig', () => {
it('when HD_AUTH_OIDC_GITLAB_USER_ID_FIELD is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_USER_ID_FIELD: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -1003,11 +939,9 @@ describe('authConfig', () => {
it('when HD_AUTH_OIDC_GITLAB_DISPLAY_NAME_FIELD is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_DISPLAY_NAME_FIELD: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -1038,11 +972,9 @@ describe('authConfig', () => {
it('when HD_AUTH_OIDC_GITLAB_PROFILE_PICTURE_FIELD is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_PROFILE_PICTURE_FIELD: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -1073,11 +1005,9 @@ describe('authConfig', () => {
it('when HD_AUTH_OIDC_GITLAB_EMAIL_FIELD is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_EMAIL_FIELD: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -1108,11 +1038,9 @@ describe('authConfig', () => {
it('when HD_AUTH_OIDC_GITLAB_ENABLE_REGISTRATION is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_ENABLE_REGISTRATION: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -1142,12 +1070,12 @@ describe('authConfig', () => {
});
});
describe('throws error', () => {
let spyConsoleError: jest.SpyInstance;
let spyConsoleError: SpyInstance;
let spyProcessExit: jest.Mock;
let originalProcess: typeof process;
beforeEach(() => {
spyConsoleError = jest.spyOn(console, 'error').mockImplementation();
spyConsoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
spyProcessExit = jest.fn();
originalProcess = global.process;
global.process = {
@@ -1164,11 +1092,9 @@ describe('authConfig', () => {
it('when HD_AUTH_OIDC_GITLAB_ISSUER is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_ISSUER: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -1182,11 +1108,9 @@ describe('authConfig', () => {
it('when HD_AUTH_OIDC_GITLAB_CLIENT_ID is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_CLIENT_ID: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -1202,11 +1126,9 @@ describe('authConfig', () => {
it('when HD_AUTH_OIDC_GITLAB_CLIENT_SECRET is not set', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_CLIENT_SECRET: undefined,
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -1222,11 +1144,9 @@ describe('authConfig', () => {
it('when HD_AUTH_OIDC_GITLAB_THEME is set to a wrong value', () => {
const restore = mockedEnv(
{
/* oxlint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_THEME: 'something else',
/* oxlint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import mockedEnv from 'mocked-env';
import customizationConfig from './customization.config';
@@ -43,7 +45,7 @@ describe('customizationConfig', () => {
});
describe('throws error', () => {
let spyConsoleError: jest.SpyInstance;
let spyConsoleError: SpyInstance;
let spyProcessExit: jest.Mock;
let originalProcess: typeof process;
+3 -1
View File
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import * as utilsModule from './utils';
import mockedEnv from 'mocked-env';
@@ -249,7 +251,7 @@ describe('databaseConfig', () => {
});
describe('throws error', () => {
let spyConsoleError: jest.SpyInstance;
let spyConsoleError: SpyInstance;
let spyProcessExit: jest.Mock;
let originalProcess: typeof process;
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import mockedEnv from 'mocked-env';
import externalServicesConfig from './external-services.config';
@@ -28,7 +30,7 @@ describe('externalServices', () => {
});
describe('throws error', () => {
let spyConsoleError: jest.SpyInstance;
let spyConsoleError: SpyInstance;
let spyProcessExit: jest.Mock;
let originalProcess: typeof process;
+3 -1
View File
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import { MediaBackendType } from '@hedgedoc/commons';
import mockedEnv from 'mocked-env';
@@ -181,7 +183,7 @@ describe('mediaConfig', () => {
});
describe('throws error', () => {
let spyConsoleError: jest.SpyInstance;
let spyConsoleError: SpyInstance;
let spyProcessExit: jest.Mock;
let originalProcess: typeof process;
+3 -1
View File
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import { PermissionLevel, PermissionLevelNames } from '@hedgedoc/commons';
import mockedEnv from 'mocked-env';
@@ -301,7 +303,7 @@ describe('noteConfig', () => {
});
describe('throws error', () => {
let spyConsoleError: jest.SpyInstance;
let spyConsoleError: SpyInstance;
let spyProcessExit: jest.Mock;
let originalProcess: typeof process;
+3 -1
View File
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import mockedEnv from 'mocked-env';
import securityConfig from './security.config';
@@ -131,7 +133,7 @@ describe('securityConfig: rate limiting', () => {
});
describe('throws error', () => {
let spyConsoleError: jest.SpyInstance;
let spyConsoleError: SpyInstance;
let spyProcessExit: jest.Mock;
let originalProcess: typeof process;
+1
View File
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, jest } from '@jest/globals';
import { Loglevel } from './loglevel.enum';
import {
ensureNoDuplicatesExist,
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect } from '@jest/globals';
import z from 'zod';
import { extractDescriptionFromZodIssue } from './zod-error-message';
@@ -11,7 +11,7 @@ export function extendKnexQueryBuilder() {
QueryBuilder.extend('whereEqualLowercase', function (field: string, value: string) {
return this.whereRaw('LOWER(??) = ?', [field, value.toLowerCase()]);
});
} catch (e) {
} catch {
console.warn('Could not extend KnexQueryBuilder with whereEqualLowercase');
}
}
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { Tracker } from 'knex-mock-client';
import { expect } from '@jest/globals';
export const IS_FIRST = 1;
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeAll, beforeEach, afterEach, jest } from '@jest/globals';
import { SortMode } from '@hedgedoc/commons';
import { OptionalNoteType, OptionalSortMode } from '@hedgedoc/commons';
import {
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect } from '@jest/globals';
import { AuthProviderType, PermissionLevel, PermissionLevelNames } from '@hedgedoc/commons';
import { ConfigModule, registerAs } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
@@ -73,12 +74,16 @@ describe('FrontendConfigService', () => {
enableRegistration: true,
},
];
for (const authConfigConfigured of [ldap, oidc]) {
for (const [providerType, authConfigConfigured] of [
['ldap', ldap],
['oidc', oidc],
] as const) {
it(`works with ${JSON.stringify(authConfigConfigured)}`, async () => {
const appConfig: AppConfig = {
baseUrl: domain,
rendererBaseUrl: 'https://renderer.example.org',
backendPort: 3000,
backendBindIp: '127.0.0.1',
log: {
level: Loglevel.ERROR,
showTimestamp: false,
@@ -86,9 +91,9 @@ describe('FrontendConfigService', () => {
};
const authConfig: AuthConfig = {
...emptyAuthConfig,
...authConfigConfigured,
[providerType]: authConfigConfigured,
};
const module: TestingModule = await Test.createTestingModule({
const testingModule: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
isGlobal: true,
@@ -128,7 +133,7 @@ describe('FrontendConfigService', () => {
],
providers: [FrontendConfigService],
}).compile();
const service = module.get(FrontendConfigService);
const service = testingModule.get(FrontendConfigService);
const config = await service.getFrontendConfig();
if (authConfig.local.enableLogin) {
expect(config.authProviders).toContainEqual({
@@ -179,6 +184,7 @@ describe('FrontendConfigService', () => {
baseUrl: domain,
rendererBaseUrl: 'https://renderer.example.org',
backendPort: 3000,
backendBindIp: '127.0.0.1',
log: {
level: Loglevel.ERROR,
showTimestamp: false,
@@ -214,6 +220,7 @@ describe('FrontendConfigService', () => {
default: {
everyone: PermissionLevel.READ,
loggedIn: PermissionLevel.WRITE,
publiclyVisible: false,
},
maxGuestLevel: PermissionLevel.FULL,
},
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeAll, beforeEach, afterEach, jest } from '@jest/globals';
import {
FieldNameGroup,
FieldNameGroupUser,
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect } from '@jest/globals';
import { ConsoleLoggerService } from './console-logger.service';
describe('sanitize', () => {
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import { MediaBackendType } from '@hedgedoc/commons';
import * as MinioModule from 'minio';
import { Client, ClientOptions } from 'minio';
@@ -26,7 +28,7 @@ describe('s3 backend', () => {
});
let mockedClient: Client;
let clientConstructorSpy: jest.SpyInstance<Client, [options: ClientOptions]>;
let clientConstructorSpy: SpyInstance<(params: ClientOptions) => Client>;
beforeEach(() => {
mockedClient = Mock.of<Client>({
+1
View File
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeAll, afterEach, jest } from '@jest/globals';
import {
FieldNameAlias,
FieldNameMediaUpload,
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import { Test, TestingModule } from '@nestjs/testing';
import { Knex } from 'knex';
import { getConnectionToken } from 'nest-knexjs';
+91 -78
View File
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeAll, beforeEach, afterEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import { PermissionLevel } from '@hedgedoc/commons';
import {
FieldNameAlias,
@@ -11,19 +13,21 @@ import {
NoteType,
TableAlias,
TableNote,
type Revision,
} from '@hedgedoc/database';
import { Provider } from '@nestjs/common';
import type { Provider } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Test, TestingModule } from '@nestjs/testing';
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import type { Tracker } from 'knex-mock-client';
import { DateTime } from 'luxon';
import type { DateTime } from 'luxon';
import { AliasService } from '../alias/alias.service';
import appConfigMock from '../config/mock/app.config.mock';
import databaseConfigMock from '../config/mock/database.config.mock';
import { createDefaultMockNoteConfig, registerNoteConfig } from '../config/mock/note.config.mock';
import { NoteConfig } from '../config/note.config';
import type { NoteConfig } from '../config/note.config';
import { expectBindings } from '../database/mock/expect-bindings';
import { mockDelete, mockInsert, mockSelect } from '../database/mock/mock-queries';
import { mockKnexDb } from '../database/mock/provider';
@@ -34,15 +38,18 @@ import {
MaximumDocumentLengthExceededError,
NotInDBError,
} from '../errors/errors';
import { NoteEvent, NoteEventMap } from '../events';
import type { NoteEventMap } from '../events';
import { NoteEvent } from '../events';
import { GroupsService } from '../groups/groups.service';
import { LoggerModule } from '../logger/logger.module';
import { PermissionService } from '../permissions/permission.service';
import { RealtimeNoteStore } from '../realtime/realtime-note/realtime-note-store';
import type { RealtimeNote } from '../realtime/realtime-note/realtime-note';
import { RevisionsService } from '../revisions/revisions.service';
import { UsersService } from '../users/users.service';
import { dateTimeToDB, getCurrentDateTime } from '../utils/datetime';
import { NoteService } from './note.service';
import { Mock } from 'ts-mockery';
describe('NoteService', () => {
let service: NoteService;
@@ -84,7 +91,7 @@ describe('NoteService', () => {
beforeAll(async () => {
[tracker, knexProvider] = mockKnexDb();
const module: TestingModule = await Test.createTestingModule({
const testingModule: TestingModule = await Test.createTestingModule({
providers: [
NoteService,
knexProvider,
@@ -105,13 +112,13 @@ describe('NoteService', () => {
],
}).compile();
service = module.get<NoteService>(NoteService);
aliasService = module.get<AliasService>(AliasService);
eventEmitter = module.get<EventEmitter2<NoteEventMap>>(EventEmitter2<NoteEventMap>);
revisionService = module.get<RevisionsService>(RevisionsService);
realtimeNoteStore = module.get<RealtimeNoteStore>(RealtimeNoteStore);
groupsService = module.get<GroupsService>(GroupsService);
permissionService = module.get<PermissionService>(PermissionService);
service = testingModule.get<NoteService>(NoteService);
aliasService = testingModule.get<AliasService>(AliasService);
eventEmitter = testingModule.get<EventEmitter2<NoteEventMap>>(EventEmitter2<NoteEventMap>);
revisionService = testingModule.get<RevisionsService>(RevisionsService);
realtimeNoteStore = testingModule.get<RealtimeNoteStore>(RealtimeNoteStore);
groupsService = testingModule.get<GroupsService>(GroupsService);
permissionService = testingModule.get<PermissionService>(PermissionService);
});
afterEach(() => {
@@ -202,12 +209,12 @@ describe('NoteService', () => {
],
])('inserts a new note', (everyoneLevel, loggedInLevel, inputAlias, outputAlias, descr) => {
let result: number;
let mockEnsureAliasIsAvailable: jest.SpyInstance;
let mockGenerateRandomAlias: jest.SpyInstance;
let mockAddAlias: jest.SpyInstance;
let mockCreateRevision: jest.SpyInstance;
let mockGetGroupIdByName: jest.SpyInstance;
let mockSetGroupPermission: jest.SpyInstance;
let mockEnsureAliasIsAvailable: SpyInstance<typeof aliasService.ensureAliasIsAvailable>;
let mockGenerateRandomAlias: SpyInstance<typeof aliasService.generateRandomAlias>;
let mockAddAlias: SpyInstance<typeof aliasService.addAlias>;
let mockCreateRevision: SpyInstance<typeof revisionService.createRevision>;
let mockGetGroupIdByName: SpyInstance<typeof groupsService.getGroupIdByName>;
let mockSetGroupPermission: SpyInstance<typeof permissionService.setGroupPermission>;
beforeEach(() => {
mockEnsureAliasIsAvailable = jest
.spyOn(aliasService, 'ensureAliasIsAvailable')
@@ -235,23 +242,6 @@ describe('NoteService', () => {
[{ [FieldNameNote.id]: mockNoteId }],
);
});
afterEach(() => {
expect(mockCreateRevision).toHaveBeenCalledWith(
mockNoteId,
mockNoteContent,
true,
expect.anything(),
);
expect(result).toBe(mockNoteId);
expectBindings(tracker, 'insert', [
[
dateTimeToDB(now),
mockOwnerUserId,
noteMockConfig.permissions.default.publiclyVisible,
2,
],
]);
});
it(`with settings: ${descr}`, async () => {
noteMockConfig.permissions.default.everyone = everyoneLevel as
@@ -297,42 +287,62 @@ describe('NoteService', () => {
expect.anything(),
);
}
expect(mockCreateRevision).toHaveBeenCalledWith(
mockNoteId,
mockNoteContent,
true,
expect.anything(),
);
expect(result).toBe(mockNoteId);
expectBindings(tracker, 'insert', [
[
dateTimeToDB(now),
mockOwnerUserId,
noteMockConfig.permissions.default.publiclyVisible,
2,
],
]);
});
});
});
/* oxlint-enable jest/no-conditional-expect */
describe('getNoteContent', () => {
let realtimeNoteStoreSpy: jest.SpyInstance;
let revsisionServiceSpy: jest.SpyInstance;
let realtimeNoteStoreSpy: SpyInstance<typeof realtimeNoteStore.find>;
let revsisionServiceSpy: SpyInstance<typeof revisionService.getLatestRevision>;
beforeEach(() => {
realtimeNoteStoreSpy = jest.spyOn(realtimeNoteStore, 'find');
revsisionServiceSpy = jest.spyOn(revisionService, 'getLatestRevision');
});
it('returns content from RealtimeNoteStore if note is active', async () => {
realtimeNoteStoreSpy.mockReturnValue({
getRealtimeDoc: () => ({
getCurrentContent: () => mockNoteContent,
realtimeNoteStoreSpy.mockReturnValue(
Mock.of<RealtimeNote>({
getRealtimeDoc: () => ({
getCurrentContent: () => mockNoteContent,
}),
}),
});
);
const result = await service.getNoteContent(mockNoteId);
expect(result).toEqual(mockNoteContent);
});
it('returns latest revision otherwise', async () => {
realtimeNoteStoreSpy.mockReturnValue(undefined);
revsisionServiceSpy.mockReturnValue({
content: mockNoteContent,
});
revsisionServiceSpy.mockReturnValue(
Promise.resolve(
Mock.of<Revision>({
content: mockNoteContent,
}),
),
);
const result = await service.getNoteContent(mockNoteId);
expect(result).toEqual(mockNoteContent);
});
});
describe('getNoteIdByAlias', () => {
let aliasServiceSpy: jest.SpyInstance;
// oxlint-disable-next-line func-style
let aliasServiceSpy: SpyInstance<typeof aliasService.isAliasForbidden>;
const buildMockSelect = (returnValues: unknown) => {
mockSelect(
tracker,
@@ -380,46 +390,45 @@ describe('NoteService', () => {
});
describe('deleteNote', () => {
let eventEmitterSpy: jest.SpyInstance;
let eventEmitterSpy: SpyInstance<typeof eventEmitter.emit>;
beforeEach(() => {
eventEmitterSpy = jest.spyOn(eventEmitter, 'emit').mockReturnValue(true);
});
afterEach(() => {
expect(eventEmitterSpy).toHaveBeenCalledWith(NoteEvent.DELETION, mockNoteId);
});
it('throws NotInDBError if note not found', async () => {
mockDelete(tracker, TableNote, [FieldNameNote.id], 0);
await expect(service.deleteNote(mockNoteId)).rejects.toThrow(NotInDBError);
expectBindings(tracker, 'delete', [[mockNoteId]]);
expect(eventEmitterSpy).toHaveBeenCalledWith(NoteEvent.DELETION, mockNoteId);
});
it('deletes a note by id', async () => {
mockDelete(tracker, TableNote, [FieldNameNote.id], 1);
await service.deleteNote(mockNoteId);
expectBindings(tracker, 'delete', [[mockNoteId]]);
expect(eventEmitterSpy).toHaveBeenCalledWith(NoteEvent.DELETION, mockNoteId);
});
});
describe('updateNote', () => {
let eventEmitterSpy: jest.SpyInstance;
let revisionServiceSpy: jest.SpyInstance;
let eventEmitterSpy: SpyInstance<typeof eventEmitter.emit>;
let revisionServiceSpy: SpyInstance<typeof revisionService.createRevision>;
beforeEach(() => {
eventEmitterSpy = jest.spyOn(eventEmitter, 'emit').mockReturnValue(true);
revisionServiceSpy = jest
.spyOn(revisionService, 'createRevision')
.mockImplementation(async () => {});
});
afterEach(() => {
expect(eventEmitterSpy).toHaveBeenCalledWith(NoteEvent.CLOSE_REALTIME, mockNoteId);
});
it('creates a new revision', async () => {
await service.updateNote(mockNoteId, mockNoteContent);
expect(revisionServiceSpy).toHaveBeenCalledWith(mockNoteId, mockNoteContent);
expect(eventEmitterSpy).toHaveBeenCalledWith(NoteEvent.CLOSE_REALTIME, mockNoteId);
});
});
describe('toNoteMetadataDto', () => {
let spyAliasService: jest.SpyInstance;
let spyAliasService: SpyInstance<typeof aliasService.getAllAliases>;
beforeEach(() => {
jest.useFakeTimers();
@@ -431,20 +440,22 @@ describe('NoteService', () => {
});
it('throws NotInDBError if the note does not have a primary alias', async () => {
spyAliasService.mockReturnValue([]);
spyAliasService.mockReturnValue(Promise.resolve([]));
await expect(service.toNoteMetadataDto(mockNoteId)).rejects.toThrow(NotInDBError);
});
it('throws NotInDBError if the note does not exist', async () => {
spyAliasService.mockReturnValue([
{
[FieldNameAlias.alias]: mockAliasRandom,
[FieldNameAlias.isPrimary]: true,
},
{
[FieldNameAlias.alias]: mockAliasCustom,
[FieldNameAlias.isPrimary]: false,
},
]);
spyAliasService.mockReturnValue(
Promise.resolve([
{
[FieldNameAlias.alias]: mockAliasRandom,
[FieldNameAlias.isPrimary]: true,
},
{
[FieldNameAlias.alias]: mockAliasCustom,
[FieldNameAlias.isPrimary]: false,
},
]),
);
mockSelect(
tracker,
[FieldNameNote.createdAt, FieldNameNote.version],
@@ -456,16 +467,18 @@ describe('NoteService', () => {
expectBindings(tracker, 'select', [[mockNoteId]], true);
});
it('returns correct NoteMetadataDto', async () => {
spyAliasService.mockReturnValue([
{
[FieldNameAlias.alias]: mockAliasRandom,
[FieldNameAlias.isPrimary]: true,
},
{
[FieldNameAlias.alias]: mockAliasCustom,
[FieldNameAlias.isPrimary]: false,
},
]);
spyAliasService.mockReturnValue(
Promise.resolve([
{
[FieldNameAlias.alias]: mockAliasRandom,
[FieldNameAlias.isPrimary]: true,
},
{
[FieldNameAlias.alias]: mockAliasCustom,
[FieldNameAlias.isPrimary]: false,
},
]),
);
jest.spyOn(revisionService, 'getLatestRevision').mockResolvedValue({
[FieldNameRevision.content]: mockNoteContent,
+5 -1
View File
@@ -306,7 +306,9 @@ export class NoteService {
transaction,
);
this.logger.debug(`Retrieved ${updateUsers.users.length} users`, 'innerToNoteMetadataDto');
updateUsers.users.sort();
updateUsers.users.sort(
(a, b) => dbToDateTime(a.createdAt).toMillis() - dbToDateTime(b.createdAt).toMillis(),
);
const updatedAt = dateTimeToISOString(
dbToDateTime(latestRevision[FieldNameRevision.createdAt]),
@@ -326,6 +328,8 @@ export class NoteService {
this.logger.debug(`updatedAt ${updatedAt}`, 'innerToNoteMetadataDto');
return NoteMetadataDto.create({
// We're expanding a DTO here which is technically a class - in this case this is fine.
// oxlint-disable-next-line typescript/no-misused-spread
...noteAliases,
title: latestRevision.title,
description: latestRevision.description,
@@ -3,21 +3,23 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import { PermissionLevel, PermissionLevelNames } from '@hedgedoc/commons';
import { ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import type { ExecutionContext } from '@nestjs/common';
import type { Reflector } from '@nestjs/core';
import { Mock } from 'ts-mockery';
import * as ExtractNoteIdOrAliasModule from '../api/utils/extract-note-id-from-request';
import { CompleteRequest } from '../api/utils/request.type';
import { ConsoleLoggerService } from '../logger/console-logger.service';
import { PermissionService } from './permission.service';
import type { CompleteRequest } from '../api/utils/request.type';
import type { ConsoleLoggerService } from '../logger/console-logger.service';
import type { PermissionService } from './permission.service';
import { PermissionsGuard } from './permissions.guard';
import { PERMISSION_METADATA_KEY } from './require-permission.decorator';
import type { extractNoteIdFromRequest } from '../api/utils/extract-note-id-from-request';
jest.mock('../api/utils/extract-note-id-from-request');
// oxlint-disable-next-line func-style
const buildContext = (userId: number | undefined, handler: () => void): ExecutionContext => {
const request = Mock.of<CompleteRequest>({
userId: userId,
@@ -38,7 +40,7 @@ describe('PermissionsGuard', () => {
let handler: () => void;
let permissionsService: PermissionService;
let permissionGuard: PermissionsGuard;
let spyOnExtractNoteId: jest.SpyInstance;
let spyOnExtractNoteId: SpyInstance<typeof extractNoteIdFromRequest>;
const mockUserId = 42;
const mockNoteId = 23;
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeAll, beforeEach, afterEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import { PermissionLevel } from '@hedgedoc/commons';
import {
FieldNameGroup,
@@ -11,6 +13,7 @@ import {
FieldNameNoteGroupPermission,
FieldNameNoteUserPermission,
FieldNameUser,
Group,
TableGroup,
TableMediaUpload,
TableNote,
@@ -44,6 +47,7 @@ import { RevisionsService } from '../revisions/revisions.service';
import { UsersService } from '../users/users.service';
import { PermissionService } from './permission.service';
import { determinePermissionTestCases } from './test/determine-permission.fixture';
import { Mock } from 'ts-mockery';
describe('PermissionsService', () => {
let service: PermissionService;
@@ -67,7 +71,7 @@ describe('PermissionsService', () => {
beforeAll(async () => {
[tracker, knexProvider] = mockKnexDb();
const module: TestingModule = await Test.createTestingModule({
const testingModule: TestingModule = await Test.createTestingModule({
providers: [
PermissionService,
knexProvider,
@@ -93,13 +97,11 @@ describe('PermissionsService', () => {
],
}).compile();
service = module.get<PermissionService>(PermissionService);
usersService = module.get<UsersService>(UsersService);
groupsService = module.get<GroupsService>(GroupsService);
service = testingModule.get<PermissionService>(PermissionService);
usersService = testingModule.get<UsersService>(UsersService);
groupsService = testingModule.get<GroupsService>(GroupsService);
});
beforeEach(() => {});
afterEach(() => {
tracker.reset();
jest.resetModules();
@@ -111,7 +113,6 @@ describe('PermissionsService', () => {
expectBindings(tracker, 'select', [[mockMediaUploadUuid]], true);
});
// oxlint-disable-next-line func-style
const buildMockSelect = (returnValues: unknown) => {
mockSelect(
tracker,
@@ -179,7 +180,6 @@ describe('PermissionsService', () => {
});
describe('isOwner', () => {
// oxlint-disable-next-line func-style
const buildMockSelect = (returnValues: unknown) => {
mockSelect(tracker, [FieldNameNote.ownerId], TableNote, FieldNameNote.id, returnValues);
};
@@ -211,7 +211,7 @@ describe('PermissionsService', () => {
});
describe('checkIfUserMayCreateNote', () => {
let spyUserServiceIsRegisteredUser: jest.SpyInstance;
let spyUserServiceIsRegisteredUser: SpyInstance<typeof usersService.isRegisteredUser>;
beforeEach(() => {
spyUserServiceIsRegisteredUser = jest.spyOn(usersService, 'isRegisteredUser');
});
@@ -232,9 +232,9 @@ describe('PermissionsService', () => {
});
describe('determinePermission', () => {
let spyOnPermissionsServiceIsOwner: jest.SpyInstance;
let spyOnUserServiceIsRegisteredUser: jest.SpyInstance;
let spyOnGroupServiceGetGroupsForUser: jest.SpyInstance;
let spyOnPermissionsServiceIsOwner: SpyInstance<typeof service.isOwner>;
let spyOnUserServiceIsRegisteredUser: SpyInstance<typeof usersService.isRegisteredUser>;
let spyOnGroupServiceGetGroupsForUser: SpyInstance<typeof groupsService.getGroupsForUser>;
beforeEach(() => {
spyOnPermissionsServiceIsOwner = jest.spyOn(service, 'isOwner');
@@ -271,16 +271,16 @@ describe('PermissionsService', () => {
// Groups
spyOnGroupServiceGetGroupsForUser.mockImplementation(() => {
const alwaysAvailableGroups = [
{ [FieldNameGroup.id]: mockGroupIdEveryone },
{ [FieldNameGroup.id]: mockGroupId1 },
Mock.of<Group>({ [FieldNameGroup.id]: mockGroupIdEveryone }),
Mock.of<Group>({ [FieldNameGroup.id]: mockGroupId1 }),
];
const loggedInGroup = {
const loggedInGroup = Mock.of<Group>({
[FieldNameGroup.id]: mockGroupIdLoggedIn,
};
});
if (testCase.isRegisteredUser) {
return [...alwaysAvailableGroups, loggedInGroup];
return Promise.resolve([...alwaysAvailableGroups, loggedInGroup]);
}
return alwaysAvailableGroups;
return Promise.resolve(alwaysAvailableGroups);
});
// GroupPermissions
mockSelect(
@@ -315,7 +315,7 @@ describe('PermissionsService', () => {
});
describe('setUserPermission', () => {
let spyOnIsOwner: jest.SpyInstance;
let spyOnIsOwner: SpyInstance<typeof service.isOwner>;
beforeEach(() => {
spyOnIsOwner = jest.spyOn(service, 'isOwner');
});
@@ -325,7 +325,7 @@ describe('PermissionsService', () => {
expect(spyOnIsOwner).toHaveBeenCalledTimes(1);
});
describe('user is not owner', () => {
let spyOnIsRegisteredUser: jest.SpyInstance;
let spyOnIsRegisteredUser: SpyInstance<typeof usersService.isRegisteredUser>;
beforeEach(() => {
spyOnIsOwner.mockResolvedValue(false);
spyOnIsRegisteredUser = jest.spyOn(usersService, 'isRegisteredUser');
@@ -338,8 +338,8 @@ describe('PermissionsService', () => {
});
it('and user is registered', async () => {
const spyOneNotifyOthers = jest.spyOn(
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
service as any,
// Typecast is required as we're mocking a private method here
service as typeof service & { notifyOthers: (_: number) => void },
'notifyOthers',
);
spyOnIsRegisteredUser.mockResolvedValue(true);
@@ -356,11 +356,11 @@ describe('PermissionsService', () => {
});
describe('removeUserPermission', () => {
let spyOneNotifyOthers: jest.SpyInstance;
let spyOneNotifyOthers: SpyInstance;
beforeEach(() => {
spyOneNotifyOthers = jest.spyOn(
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
service as any,
// Typecast is required as we're mocking a private method here
service as typeof service & { notifyOthers: (_: number) => void },
'notifyOthers',
);
});
@@ -403,8 +403,8 @@ describe('PermissionsService', () => {
describe('setGroupPermission', () => {
it('correctly sets group permissions and notifies other user', async () => {
const spyOneNotifyOthers = jest.spyOn(
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
service as any,
// Typecast is required as we're mocking a private method here
service as typeof service & { notifyOthers: (_: number) => void },
'notifyOthers',
);
mockInsert(tracker, TableNoteGroupPermission, [
@@ -419,15 +419,14 @@ describe('PermissionsService', () => {
});
describe('removeGroupPermission', () => {
let spyOneNotifyOthers: jest.SpyInstance;
let spyOneNotifyOthers: SpyInstance;
beforeEach(() => {
spyOneNotifyOthers = jest.spyOn(
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
service as any,
// Typecast is required as we're mocking a private method here
service as typeof service & { notifyOthers: (_: number) => void },
'notifyOthers',
);
});
// oxlint-disable-next-line func-style
const buildMockDelete = (deletedEntries: number) => {
mockDelete(
tracker,
@@ -453,15 +452,14 @@ describe('PermissionsService', () => {
});
describe('changeOwner', () => {
let spyOneNotifyOthers: jest.SpyInstance;
let spyOneNotifyOthers: SpyInstance;
beforeEach(() => {
spyOneNotifyOthers = jest.spyOn(
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
service as any,
// Typecast is required as we're mocking a private method here
service as typeof service & { notifyOthers: (_: number) => void },
'notifyOthers',
);
});
// oxlint-disable-next-line func-style
const buildMockUpdate = (updatedEntries: number) => {
mockUpdate(tracker, TableNote, [FieldNameNote.ownerId], FieldNameNote.id, updatedEntries);
};
@@ -480,7 +478,6 @@ describe('PermissionsService', () => {
});
describe('changePubliclyVisibly', () => {
// oxlint-disable-next-line func-style
const buildMockUpdate = (updatedEntries: number) => {
mockUpdate(
tracker,
@@ -503,7 +500,6 @@ describe('PermissionsService', () => {
});
describe('getPermissionsDtoForNote', () => {
// oxlint-disable-next-line func-style
const buildMockOwnerSelect = (returnValues: unknown) => {
mockSelect(
tracker,
@@ -523,7 +519,6 @@ describe('PermissionsService', () => {
],
);
};
// oxlint-disable-next-line func-style
const buildMockUserPermissionsSelect = (returnValues: unknown) => {
mockSelect(
tracker,
@@ -543,7 +538,6 @@ describe('PermissionsService', () => {
],
);
};
// oxlint-disable-next-line func-style
const buildMockGroupPermissionsSelect = (returnValues: unknown) => {
mockSelect(
tracker,
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect } from '@jest/globals';
import { PermissionLevel } from '@hedgedoc/commons';
import { convertEditabilityToPermissionLevel } from './convert-editability-to-permission-level';
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import {
MessageTransporter,
MockedBackendTransportAdapter,
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import * as realtimeNoteModule from './realtime-note';
import { RealtimeNote } from './realtime-note';
import { RealtimeNoteStore } from './realtime-note-store';
@@ -10,7 +12,9 @@ import { RealtimeNoteStore } from './realtime-note-store';
describe('RealtimeNoteStore', () => {
let realtimeNoteStore: RealtimeNoteStore;
let mockedRealtimeNote: RealtimeNote;
let realtimeNoteConstructorSpy: jest.SpyInstance;
let realtimeNoteConstructorSpy: SpyInstance<
(noteId: number, initialTextContent: string, initialYjsState?: number[]) => RealtimeNote
>;
const mockedContent = 'mockedContent';
const mockedNoteId = 4711;
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeAll, beforeEach, afterAll, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import { PermissionLevel } from '@hedgedoc/commons';
import { FieldNameRevision, Revision } from '@hedgedoc/database';
import { SchedulerRegistry } from '@nestjs/schedule';
@@ -31,13 +33,13 @@ describe('RealtimeNoteService', () => {
let mockedPermissionService: PermissionService;
let consoleLoggerService: ConsoleLoggerService;
let mockedNoteConfig: NoteConfig;
let addIntervalSpy: jest.SpyInstance;
let setIntervalSpy: jest.SpyInstance;
let clearIntervalSpy: jest.SpyInstance;
let addIntervalSpy: SpyInstance<typeof SchedulerRegistry.prototype.addInterval>;
let setIntervalSpy: SpyInstance<typeof setInterval>;
let clearIntervalSpy: SpyInstance<typeof clearInterval>;
let clientWithReadWrite: RealtimeConnection;
let clientWithRead: RealtimeConnection;
let clientWithoutReadWrite: RealtimeConnection;
let deleteIntervalSpy: jest.SpyInstance;
let deleteIntervalSpy: SpyInstance<typeof SchedulerRegistry.prototype.deleteInterval>;
const readWriteUserId = 2;
const onlyReadUserId = 1;
@@ -84,9 +86,9 @@ describe('RealtimeNoteService', () => {
error: jest.fn(),
});
realtimeNoteStore = Mock.of<RealtimeNoteStore>({
find: jest.fn(),
find: jest.fn<typeof RealtimeNoteStore.prototype.find>(),
create: jest.fn(),
getAllRealtimeNotes: jest.fn(),
getAllRealtimeNotes: jest.fn<typeof RealtimeNoteStore.prototype.getAllRealtimeNotes>(),
});
mockedNoteConfig = Mock.of<NoteConfig>({ persistInterval: 0 });
@@ -109,7 +111,7 @@ describe('RealtimeNoteService', () => {
addIntervalSpy = jest.spyOn(schedulerRegistry, 'addInterval');
deleteIntervalSpy = jest.spyOn(schedulerRegistry, 'deleteInterval');
setIntervalSpy = jest.spyOn(global, 'setInterval');
setIntervalSpy = jest.spyOn(global, 'setInterval') as SpyInstance<typeof setInterval>;
clearIntervalSpy = jest.spyOn(global, 'clearInterval');
clientWithReadWrite = new MockConnectionBuilder(realtimeNote)
@@ -208,8 +210,8 @@ describe('RealtimeNoteService', () => {
});
describe('handleNoteAliasesChanged', () => {
let spyRealtimeNoteStoreFind: jest.SpyInstance;
let spyAnnounceAliasesUpdate: jest.SpyInstance;
let spyRealtimeNoteStoreFind: SpyInstance<typeof realtimeNoteStore.find>;
let spyAnnounceAliasesUpdate: SpyInstance<typeof realtimeNote.announceAliasesUpdate>;
beforeEach(() => {
spyRealtimeNoteStoreFind = jest.spyOn(realtimeNoteStore, 'find').mockImplementation(() => {
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeAll, afterAll, jest } from '@jest/globals';
import { MessageType, RealtimeDoc } from '@hedgedoc/commons';
import { RealtimeNote } from './realtime-note';
@@ -3,6 +3,17 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
describe,
it,
expect,
beforeAll,
beforeEach,
afterAll,
afterEach,
jest,
} from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import {
Message,
MessageTransporter,
@@ -12,7 +23,7 @@ import {
import { RealtimeUserStatusAdapter } from './realtime-user-status-adapter';
type SendMessageSpy = jest.SpyInstance<void, [content: Message<MessageType>]>;
type SendMessageSpy = SpyInstance<typeof MessageTransporter.prototype.sendMessage>;
describe('realtime user status adapter', () => {
let clientLoggedIn1: RealtimeUserStatusAdapter | undefined;
@@ -5,6 +5,7 @@
*/
import { MockedBackendTransportAdapter, YDocSyncServerAdapter } from '@hedgedoc/commons';
import { FieldNameUser, User } from '@hedgedoc/database';
import { jest } from '@jest/globals';
import { Mock } from 'ts-mockery';
import { RealtimeConnection } from '../realtime-connection';
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import {
ConnectionState,
DisconnectReason,
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect } from '@jest/globals';
import { IncomingMessage } from 'http';
import { Mock } from 'ts-mockery';
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import { PermissionLevel } from '@hedgedoc/commons';
import { FieldNameUser } from '@hedgedoc/database';
import { Provider } from '@nestjs/common';
@@ -48,8 +50,8 @@ describe('Websocket gateway', () => {
let permissionsService: PermissionService;
let mockedWebsocketConnection: RealtimeConnection;
let mockedWebsocket: WebSocket;
let mockedWebsocketCloseSpy: jest.SpyInstance;
let addClientSpy: jest.SpyInstance;
let mockedWebsocketCloseSpy: SpyInstance<typeof WebSocket.WebSocket.prototype.close>;
let addClientSpy: SpyInstance<typeof RealtimeNote.prototype.addClient>;
const mockedValidSessionCookie = 'mockedValidSessionCookie';
const mockedSessionIdWithUser = 'mockedSessionIdWithUser';
@@ -3,6 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeAll, beforeEach, afterEach, jest } from '@jest/globals';
import type { SpyInstance } from 'jest-mock';
import {
FieldNameAlias,
FieldNameAuthorshipInfo,
@@ -155,8 +157,7 @@ describe('RevisionsService', () => {
});
describe('purgeRevisions', () => {
let spyOnGetPrimaryAlias: jest.SpyInstance;
// oxlint-disable-next-line func-style
let spyOnGetPrimaryAlias: SpyInstance<typeof aliasService.getPrimaryAliasByNoteId>;
const buildMockSelect = (returnValues: unknown) => {
mockSelect(tracker, [], TableRevision, [FieldNameRevision.noteId], returnValues);
};
@@ -134,6 +134,8 @@ export class RevisionsService {
recordMap.set(
revision[FieldNameRevision.uuid],
RevisionMetadataDto.create({
// We're extending a DTO here which is technically a class but valid in this case.
// oxlint-disable-next-line typescript/no-misused-spread
...currentMappedRevision,
authorUsernames,
authorGuestUuids,
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect } from '@jest/globals';
import { extractRevisionMetadataFromContent } from './extract-revision-metadata-from-content';
describe('revision entity', () => {
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeAll, beforeEach, afterEach, jest } from '@jest/globals';
import { AuthProviderType } from '@hedgedoc/commons';
import { FieldNameSession, Session, TableSession } from '@hedgedoc/database';
import { Session as FastifySession } from 'fastify';
+1 -1
View File
@@ -68,5 +68,5 @@ export interface SessionState {
}
declare module 'fastify' {
interface Session extends SessionState {}
interface Session extends Omit<SessionState, 'cookie'> {}
}
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeAll, afterEach } from '@jest/globals';
import { Provider } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect } from '@jest/globals';
import { generateRandomName } from './name-randomizer';
describe('name randomizer', () => {
+3
View File
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeAll, afterEach, jest } from '@jest/globals';
import { FieldNameUser, TableUser, User } from '@hedgedoc/database';
import { BadRequestException, Provider } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@@ -216,6 +217,7 @@ describe('UsersService', () => {
['returns true if username already exists', [{ [FieldNameUser.username]: username }], true],
['returns false if username does not already exists', [], false],
])('isUsernameTaken', (title, returnValue, result) => {
// oxlint-disable-next-line jest/valid-title
it(title, async () => {
mockSelect(tracker, [FieldNameUser.username], TableUser, FieldNameUser.username, returnValue);
expect(await service.isUsernameTaken(username)).toBe(result);
@@ -228,6 +230,7 @@ describe('UsersService', () => {
['returns false if user does not exist', [], false],
['returns true if user is not a guest', [{ [FieldNameUser.username]: username }], true],
])('isRegisteredUser', (title, returnValue, result) => {
// oxlint-disable-next-line jest/valid-title
it(title, async () => {
mockSelect(tracker, [FieldNameUser.username], TableUser, FieldNameUser.id, returnValue);
expect(await service.isRegisteredUser(userId)).toBe(result);
+1
View File
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, jest } from '@jest/globals';
import argon2 from '@node-rs/argon2';
import {
+1
View File
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, afterEach, jest } from '@jest/globals';
import { promises as fs } from 'fs';
import { clearCachedVersion, getServerVersionFromPackageJson } from './server-version';
+9 -11
View File
@@ -3,20 +3,16 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { BadRequestException, ValidationPipe } from '@nestjs/common';
import { BadRequestException, PipeTransform } from '@nestjs/common';
import { createZodValidationPipe } from 'nestjs-zod';
import { ZodError } from 'zod';
import { ConsoleLoggerService } from '../logger/console-logger.service';
export function setupValidationPipe(logger: ConsoleLoggerService): ValidationPipe {
// This issue is only relevant for usage of class-validator, however we use Zod
// oxlint-disable-next-line @darraghor/nestjs-typed/should-specify-forbid-unknown-values
return new ValidationPipe({
forbidUnknownValues: false,
skipMissingProperties: false,
transform: true,
exceptionFactory: (errors): BadRequestException => {
// strip the trailing newline for cleaner logs
const errorMessage = errors.toString().trimEnd();
export function setupValidationPipe(logger: ConsoleLoggerService): PipeTransform {
const ZodValidationPipe = createZodValidationPipe({
createValidationException: (error: ZodError): BadRequestException => {
const errorMessage = error.toString().trimEnd();
logger.debug(
`Errors were encountered while validating a request:\n${errorMessage}`,
'ValidationPipe',
@@ -26,4 +22,6 @@ export function setupValidationPipe(logger: ConsoleLoggerService): ValidationPip
);
},
});
return new ZodValidationPipe();
}
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import { PRIVATE_API_PREFIX } from '../../src/app.module';
import type { TestSetup } from '../test-setup';
import { noteAlias1, password3, TestSetupBuilder, username3 } from '../test-setup';
@@ -85,6 +86,7 @@ describe('Alias', () => {
});
});
describe.each(AliasTestCases)('returns the note', (testName, _aliasToUse, aliasToQuery) => {
// oxlint-disable-next-line jest/valid-title
it(testName, async () => {
const result = await agentUser1
.get(`${PRIVATE_API_PREFIX}/alias/${aliasToQuery}`)
@@ -107,6 +109,7 @@ describe('Alias', () => {
};
describe.each(AliasTestCases)('creates', (testName, alias) => {
// oxlint-disable-next-line jest/valid-title
it(testName, async () => {
newAliasDto.newAlias = alias;
await agentUser1
@@ -179,6 +182,7 @@ describe('Alias', () => {
const newAlias = DefaultTestAlias;
describe.each(AliasTestCases)('correctly set primary alias', (testName, alias) => {
// oxlint-disable-next-line jest/valid-title
it(testName, async () => {
await testSetup.aliasService.addAlias(noteId, newAlias);
await agentUser1
@@ -242,10 +246,13 @@ describe('Alias', () => {
await testSetup.aliasService.addAlias(noteId, newAlias);
});
describe.each(AliasTestCases)('correctly deletes the alias', (testName, alias) => {
// oxlint-disable-next-line jest/valid-title
it(testName, async () => {
await expect(testSetup.notesService.getNoteIdByAlias(newAlias)).resolves.toBe(noteId);
await agentUser1.delete(`${PRIVATE_API_PREFIX}/alias/${alias}`).expect(204);
await expect(testSetup.notesService.getNoteIdByAlias(newAlias)).rejects.toThrow();
await expect(testSetup.notesService.getNoteIdByAlias(newAlias)).rejects.toThrow(
'Could not find note',
);
});
});
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import request from 'supertest';
import { ApiTokenWithSecretInterface } from '@hedgedoc/commons';
import { dateTimeToISOString, getCurrentDateTime } from '../../src/utils/datetime';
@@ -7,6 +7,7 @@
@typescript-eslint/no-unsafe-assignment,
@typescript-eslint/no-unsafe-member-access
*/
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import request from 'supertest';
import { AuthProviderType, FieldNameIdentity, FieldNameUser } from '@hedgedoc/database';
import { checkPassword } from '../../src/utils/password';
@@ -1,3 +1,4 @@
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import { PRIVATE_API_PREFIX } from '../../src/app.module';
import { getServerVersionFromPackageJson } from '../../src/utils/server-version';
import { TestSetup, TestSetupBuilder } from '../test-setup';
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import { PRIVATE_API_PREFIX } from '../../src/app.module';
import { password1, TestSetup, TestSetupBuilder, username1 } from '../test-setup';
import { setupAgent } from './utils/setup-agent';
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import { SortMode } from '@hedgedoc/commons';
import { NoteType } from '@hedgedoc/database';
import { PRIVATE_API_PREFIX } from '../../src/app.module';
@@ -1,3 +1,4 @@
import { describe, test, expect, beforeEach, afterEach } from '@jest/globals';
import { PRIVATE_API_PREFIX } from '../../src/app.module';
import { createDefaultMockNoteConfig } from '../../src/config/mock/note.config.mock';
import { NoteConfig } from '../../src/config/note.config';
@@ -1,3 +1,4 @@
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import { PRIVATE_API_PREFIX } from '../../src/app.module';
import { NotInDBError } from '../../src/errors/errors';
import {
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import { promises as fs } from 'fs';
import { join } from 'path';
import type request from 'supertest';
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import { PRIVATE_API_PREFIX } from '../../src/app.module';
import type { NoteMetadataDto } from '../../src/dtos/note-metadata.dto';
import { NotInDBError } from '../../src/errors/errors';
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import request from 'supertest';
import { PRIVATE_API_PREFIX } from '../../src/app.module';
import {
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import { PUBLIC_API_PREFIX } from '../../src/app.module';
import type { AliasUpdateDto } from '../../src/dtos/alias-update.dto';
import { NotInDBError } from '../../src/errors/errors';
@@ -33,6 +34,7 @@ describe('Alias', () => {
describe(`POST ${PUBLIC_API_PREFIX}/alias`, () => {
describe.each(AliasTestCases)('create normal alias', (testName, aliasToUse, aliasToQuery) => {
// oxlint-disable-next-line jest/valid-title
it(testName, async () => {
const newAliasDto: AliasCreateDto = {
noteAlias: noteAlias1,
@@ -126,6 +128,7 @@ describe('Alias', () => {
describe.each(AliasTestCases)(
'updates a note with a normal alias',
(testName, aliasToUse, aliasToQuery) => {
// oxlint-disable-next-line jest/valid-title
it(testName, async () => {
await testSetup.aliasService.addAlias(noteId, aliasToUse);
const metadata = await agent
@@ -203,6 +206,7 @@ describe('Alias', () => {
describe.each(AliasTestCases)(
'deletes a normal alias',
(testName, aliasToUse, aliasToQuery) => {
// oxlint-disable-next-line jest/valid-title
it(testName, async () => {
await testSetup.aliasService.addAlias(testSetup.ownedNoteIds[0], aliasToUse);
@@ -244,6 +248,7 @@ describe('Alias', () => {
});
describe.each(AliasTestCases)('if alias is primary', (testName, aliasToUse, aliasToQuery) => {
// oxlint-disable-next-line jest/valid-title
it(testName, async () => {
// add another alias
await testSetup.aliasService.addAlias(testSetup.ownedNoteIds[0], aliasToUse);
@@ -1,3 +1,9 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import { PUBLIC_API_PREFIX } from '../../src/app.module';
import { NoteMetadataDto } from '../../src/dtos/note-metadata.dto';
import {
@@ -8,11 +14,6 @@ import {
TestSetupBuilder,
username1,
} from '../test-setup';
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { AuthProviderType, FieldNameUser } from '@hedgedoc/database';
import { promises as fs } from 'fs';
import { join } from 'path';
@@ -77,7 +78,7 @@ describe('Me', () => {
});
describe(`GET ${PUBLIC_API_PREFIX}/me/media`, () => {
let newNoteId1: number;
const imageIds = [];
const imageIds: string[] = [];
beforeEach(async () => {
newNoteId1 = await testSetup.notesService.getNoteIdByAlias(noteAlias1);
});
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import { PUBLIC_API_PREFIX } from '../../src/app.module';
import type { MediaUploadDto } from '../../src/dtos/media-upload.dto';
import { ConsoleLoggerService } from '../../src/logger/console-logger.service';
@@ -1,3 +1,4 @@
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import { PUBLIC_API_PREFIX } from '../../src/app.module';
import { NotePermissionsDto } from '../../src/dtos/note-permissions.dto';
import { NotInDBError } from '../../src/errors/errors';
+2 -1
View File
@@ -1,6 +1,7 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"strict": false
"strict": false,
"rootDir": ".."
}
}
+1
View File
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { expect } from '@jest/globals';
import { promises as fs } from 'fs';
import request from 'supertest';
import { PUBLIC_API_PREFIX } from '../src/app.module';
+1 -1
View File
@@ -9,9 +9,9 @@
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"strict": true,
"strictNullChecks": true,
"strictPropertyInitialization": false,
"resolveJsonModule": true,
"useDefineForClassFields": false,
+60 -3
View File
@@ -2862,6 +2862,7 @@ __metadata:
"@fastify/static": "npm:9.0.0"
"@hedgedoc/commons": "workspace:commons"
"@hedgedoc/database": "workspace:database"
"@jest/types": "npm:30.3.0"
"@mrdrogdrog/optional": "npm:1.2.1"
"@nestjs/cli": "npm:10.4.9"
"@nestjs/common": "npm:10.4.22"
@@ -2881,7 +2882,6 @@ __metadata:
"@types/cli-color": "npm:2.0.6"
"@types/cookie": "npm:1.0.0"
"@types/cookie-signature": "npm:1.1.2"
"@types/jest": "npm:29.5.14"
"@types/luxon": "npm:3.7.1"
"@types/markdown-it": "npm:13.0.8"
"@types/node": "npm:24.10.7"
@@ -3561,6 +3561,16 @@ __metadata:
languageName: node
linkType: hard
"@jest/pattern@npm:30.0.1":
version: 30.0.1
resolution: "@jest/pattern@npm:30.0.1"
dependencies:
"@types/node": "npm:*"
jest-regex-util: "npm:30.0.1"
checksum: 10c0/32c5a7bfb6c591f004dac0ed36d645002ed168971e4c89bd915d1577031672870032594767557b855c5bc330aa1e39a2f54bf150d2ee88a7a0886e9cb65318bc
languageName: node
linkType: hard
"@jest/reporters@npm:^29.7.0":
version: 29.7.0
resolution: "@jest/reporters@npm:29.7.0"
@@ -3598,6 +3608,15 @@ __metadata:
languageName: node
linkType: hard
"@jest/schemas@npm:30.0.5":
version: 30.0.5
resolution: "@jest/schemas@npm:30.0.5"
dependencies:
"@sinclair/typebox": "npm:^0.34.0"
checksum: 10c0/449dcd7ec5c6505e9ac3169d1143937e67044ae3e66a729ce4baf31812dfd30535f2b3b2934393c97cfdf5984ff581120e6b38f62b8560c8b5b7cc07f4175f65
languageName: node
linkType: hard
"@jest/schemas@npm:^29.6.3":
version: 29.6.3
resolution: "@jest/schemas@npm:29.6.3"
@@ -3679,6 +3698,21 @@ __metadata:
languageName: node
linkType: hard
"@jest/types@npm:30.3.0":
version: 30.3.0
resolution: "@jest/types@npm:30.3.0"
dependencies:
"@jest/pattern": "npm:30.0.1"
"@jest/schemas": "npm:30.0.5"
"@types/istanbul-lib-coverage": "npm:^2.0.6"
"@types/istanbul-reports": "npm:^3.0.4"
"@types/node": "npm:*"
"@types/yargs": "npm:^17.0.33"
chalk: "npm:^4.1.2"
checksum: 10c0/c3e3f4de0b77a7ced345f47d3687b1094c1b6c1521529a7ca66a76f9a80194f79179a1dbc32d6761a5b67914a8f78be1e65d1408107efcb1f252c4a63b5ddd92
languageName: node
linkType: hard
"@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.2, @jridgewell/gen-mapping@npm:^0.3.5":
version: 0.3.5
resolution: "@jridgewell/gen-mapping@npm:0.3.5"
@@ -5242,6 +5276,13 @@ __metadata:
languageName: node
linkType: hard
"@sinclair/typebox@npm:^0.34.0":
version: 0.34.49
resolution: "@sinclair/typebox@npm:0.34.49"
checksum: 10c0/16b7d87f039a49b68c10bb4cdcae2ce5242b2472228851fd6483731616aba4ef977690aa517b230a8d20da8185bb416eb34e326f30568b3963c1cf26b05d1ad8
languageName: node
linkType: hard
"@sindresorhus/merge-streams@npm:^1.0.0":
version: 1.0.0
resolution: "@sindresorhus/merge-streams@npm:1.0.0"
@@ -6144,7 +6185,7 @@ __metadata:
languageName: node
linkType: hard
"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1":
"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1, @types/istanbul-lib-coverage@npm:^2.0.6":
version: 2.0.6
resolution: "@types/istanbul-lib-coverage@npm:2.0.6"
checksum: 10c0/3948088654f3eeb45363f1db158354fb013b362dba2a5c2c18c559484d5eb9f6fd85b23d66c0a7c2fcfab7308d0a585b14dadaca6cc8bf89ebfdc7f8f5102fb7
@@ -6160,7 +6201,7 @@ __metadata:
languageName: node
linkType: hard
"@types/istanbul-reports@npm:^3.0.0":
"@types/istanbul-reports@npm:^3.0.0, @types/istanbul-reports@npm:^3.0.4":
version: 3.0.4
resolution: "@types/istanbul-reports@npm:3.0.4"
dependencies:
@@ -6485,6 +6526,15 @@ __metadata:
languageName: node
linkType: hard
"@types/yargs@npm:^17.0.33":
version: 17.0.35
resolution: "@types/yargs@npm:17.0.35"
dependencies:
"@types/yargs-parser": "npm:*"
checksum: 10c0/609557826a6b85e73ccf587923f6429850d6dc70e420b455bab4601b670bfadf684b09ae288bccedab042c48ba65f1666133cf375814204b544009f57d6eef63
languageName: node
linkType: hard
"@types/yargs@npm:^17.0.8":
version: 17.0.32
resolution: "@types/yargs@npm:17.0.32"
@@ -12113,6 +12163,13 @@ __metadata:
languageName: node
linkType: hard
"jest-regex-util@npm:30.0.1":
version: 30.0.1
resolution: "jest-regex-util@npm:30.0.1"
checksum: 10c0/f30c70524ebde2d1012afe5ffa5691d5d00f7d5ba9e43d588f6460ac6fe96f9e620f2f9b36a02d0d3e7e77bc8efb8b3450ae3b80ac53c8be5099e01bf54f6728
languageName: node
linkType: hard
"jest-regex-util@npm:^29.6.3":
version: 29.6.3
resolution: "jest-regex-util@npm:29.6.3"