summaryrefslogtreecommitdiff
path: root/packages/backend/test
diff options
context:
space:
mode:
authordakkar <dakkar@thenautilus.net>2025-07-31 21:53:33 +0000
committerdakkar <dakkar@thenautilus.net>2025-07-31 21:53:33 +0000
commita2bc6603c244290707a7aadb661833fa74c69460 (patch)
tree4def24de3c615351ba6ab86390029b393a81fa2f /packages/backend/test
parentmerge: disable outgoing mastodon quotes *FOR STABLE* (!1169) (diff)
parentmerge: Improve URL validation *FOR STABLE* (!1191) (diff)
downloadsharkey-a2bc6603c244290707a7aadb661833fa74c69460.tar.gz
sharkey-a2bc6603c244290707a7aadb661833fa74c69460.tar.bz2
sharkey-a2bc6603c244290707a7aadb661833fa74c69460.zip
merge: For 2025.4.4 (!1199)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1199 Approved-by: Hazelnoot <acomputerdog@gmail.com> Approved-by: Marie <github@yuugi.dev>
Diffstat (limited to 'packages/backend/test')
-rw-r--r--packages/backend/test/unit/FetchInstanceMetadataService.ts2
-rw-r--r--packages/backend/test/unit/core/HttpRequestService.ts65
-rw-r--r--packages/backend/test/unit/core/activitypub/ApUtilityService.ts140
-rw-r--r--packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts36
4 files changed, 214 insertions, 29 deletions
diff --git a/packages/backend/test/unit/FetchInstanceMetadataService.ts b/packages/backend/test/unit/FetchInstanceMetadataService.ts
index 1e3605aafc..812ee38703 100644
--- a/packages/backend/test/unit/FetchInstanceMetadataService.ts
+++ b/packages/backend/test/unit/FetchInstanceMetadataService.ts
@@ -16,6 +16,7 @@ import { HttpRequestService } from '@/core/HttpRequestService.js';
import { LoggerService } from '@/core/LoggerService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { IdService } from '@/core/IdService.js';
+import { EnvService } from '@/core/EnvService.js';
import { DI } from '@/di-symbols.js';
function mockRedis() {
@@ -46,6 +47,7 @@ describe('FetchInstanceMetadataService', () => {
LoggerService,
UtilityService,
IdService,
+ EnvService,
],
})
.useMocker((token) => {
diff --git a/packages/backend/test/unit/core/HttpRequestService.ts b/packages/backend/test/unit/core/HttpRequestService.ts
index a2f4604e7b..ccce32ffee 100644
--- a/packages/backend/test/unit/core/HttpRequestService.ts
+++ b/packages/backend/test/unit/core/HttpRequestService.ts
@@ -3,11 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { jest } from '@jest/globals';
+import { describe, jest } from '@jest/globals';
import type { Mock } from 'jest-mock';
import type { PrivateNetwork } from '@/config.js';
import type { Socket } from 'net';
-import { HttpRequestService, isPrivateIp, validateSocketConnect } from '@/core/HttpRequestService.js';
+import { HttpRequestService, isAllowedPrivateIp, isPrivateUrl, resolveIp, validateSocketConnect } from '@/core/HttpRequestService.js';
import { parsePrivateNetworks } from '@/config.js';
describe(HttpRequestService, () => {
@@ -21,38 +21,85 @@ describe(HttpRequestService, () => {
]);
});
- describe('isPrivateIp', () => {
+ describe(isAllowedPrivateIp, () => {
it('should return false when ip public', () => {
- const result = isPrivateIp(allowedPrivateNetworks, '74.125.127.100', 80);
+ const result = isAllowedPrivateIp(allowedPrivateNetworks, '74.125.127.100', 80);
expect(result).toBeFalsy();
});
it('should return false when ip private and port matches', () => {
- const result = isPrivateIp(allowedPrivateNetworks, '127.0.0.1', 1);
+ const result = isAllowedPrivateIp(allowedPrivateNetworks, '127.0.0.1', 1);
expect(result).toBeFalsy();
});
it('should return false when ip private and all ports undefined', () => {
- const result = isPrivateIp(allowedPrivateNetworks, '10.0.0.1', undefined);
+ const result = isAllowedPrivateIp(allowedPrivateNetworks, '10.0.0.1', undefined);
expect(result).toBeFalsy();
});
it('should return true when ip private and no ports specified', () => {
- const result = isPrivateIp(allowedPrivateNetworks, '10.0.0.2', 80);
+ const result = isAllowedPrivateIp(allowedPrivateNetworks, '10.0.0.2', 80);
expect(result).toBeTruthy();
});
it('should return true when ip private and port does not match', () => {
- const result = isPrivateIp(allowedPrivateNetworks, '127.0.0.1', 80);
+ const result = isAllowedPrivateIp(allowedPrivateNetworks, '127.0.0.1', 80);
expect(result).toBeTruthy();
});
it('should return true when ip private and port is null but ports are specified', () => {
- const result = isPrivateIp(allowedPrivateNetworks, '127.0.0.1', undefined);
+ const result = isAllowedPrivateIp(allowedPrivateNetworks, '127.0.0.1', undefined);
expect(result).toBeTruthy();
});
});
+ const fakeLookup = (host: string, _: unknown, callback: (err: Error | null, ip: string) => void) => {
+ if (host === 'localhost') {
+ callback(null, '127.0.0.1');
+ } else {
+ callback(null, '23.192.228.80');
+ }
+ };
+
+ describe(resolveIp, () => {
+ it('should parse inline IPs', async () => {
+ const result = await resolveIp(new URL('https://10.0.0.1'), fakeLookup);
+ expect(result.toString()).toEqual('10.0.0.1');
+ });
+
+ it('should resolve domain names', async () => {
+ const result = await resolveIp(new URL('https://localhost'), fakeLookup);
+ expect(result.toString()).toEqual('127.0.0.1');
+ });
+ });
+
+ describe(isPrivateUrl, () => {
+ it('should return false when URL is public host', async () => {
+ const result = await isPrivateUrl(new URL('https://example.com'), fakeLookup);
+ expect(result).toBe(false);
+ });
+
+ it('should return true when URL is private host', async () => {
+ const result = await isPrivateUrl(new URL('https://localhost'), fakeLookup);
+ expect(result).toBe(true);
+ });
+
+ it('should return false when IP is public', async () => {
+ const result = await isPrivateUrl(new URL('https://23.192.228.80'), fakeLookup);
+ expect(result).toBe(false);
+ });
+
+ it('should return true when IP is private', async () => {
+ const result = await isPrivateUrl(new URL('https://127.0.0.1'), fakeLookup);
+ expect(result).toBe(true);
+ });
+
+ it('should return true when IP is private with port and path', async () => {
+ const result = await isPrivateUrl(new URL('https://127.0.0.1:443/some/path'), fakeLookup);
+ expect(result).toBe(true);
+ });
+ });
+
describe('validateSocketConnect', () => {
let fakeSocket: Socket;
let fakeSocketMutable: {
diff --git a/packages/backend/test/unit/core/activitypub/ApUtilityService.ts b/packages/backend/test/unit/core/activitypub/ApUtilityService.ts
index 325a94dc5a..7b564b1fdd 100644
--- a/packages/backend/test/unit/core/activitypub/ApUtilityService.ts
+++ b/packages/backend/test/unit/core/activitypub/ApUtilityService.ts
@@ -3,30 +3,52 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import type { UtilityService } from '@/core/UtilityService.js';
import type { IObject } from '@/core/activitypub/type.js';
import type { EnvService } from '@/core/EnvService.js';
+import type { MiMeta } from '@/models/Meta.js';
+import type { Config } from '@/config.js';
+import type { LoggerService } from '@/core/LoggerService.js';
+import Logger from '@/logger.js';
import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js';
+import { UtilityService } from '@/core/UtilityService.js';
describe(ApUtilityService, () => {
let serviceUnderTest: ApUtilityService;
let env: Record<string, string>;
beforeEach(() => {
- const utilityService = {
- punyHostPSLDomain(input: string) {
- const host = new URL(input).host;
- const parts = host.split('.');
- return `${parts[parts.length - 2]}.${parts[parts.length - 1]}`;
- },
- } as unknown as UtilityService;
-
env = {};
const envService = {
env,
} as unknown as EnvService;
- serviceUnderTest = new ApUtilityService(utilityService, envService);
+ const config = {
+ host: 'example.com',
+ blockedHosts: [],
+ silencedHosts: [],
+ mediaSilencedHosts: [],
+ federationHosts: [],
+ bubbleInstances: [],
+ deliverSuspendedSoftware: [],
+ federation: 'all',
+ } as unknown as Config;
+ const meta = {
+
+ } as MiMeta;
+
+ const utilityService = new UtilityService(config, meta, envService);
+
+ const loggerService = {
+ getLogger(domain: string) {
+ const logger = new Logger(domain);
+ Object.defineProperty(logger, 'log', {
+ value: () => {},
+ });
+ return logger;
+ },
+ } as unknown as LoggerService;
+
+ serviceUnderTest = new ApUtilityService(utilityService, loggerService);
});
describe('assertIdMatchesUrlAuthority', () => {
@@ -351,4 +373,102 @@ describe(ApUtilityService, () => {
expect(result).toBe('http://example.com/1');
});
});
+
+ describe('sanitizeInlineObject', () => {
+ it('should exclude nested arrays', () => {
+ const input = {
+ test: [[]] as unknown as string[],
+ };
+
+ const result = serviceUnderTest.sanitizeInlineObject(input, 'test', 'https://example.com/actor', 'example.com');
+
+ expect(result).toBe(false);
+ });
+
+ it('should exclude incorrect type', () => {
+ const input = {
+ test: 0 as unknown as string,
+ };
+
+ const result = serviceUnderTest.sanitizeInlineObject(input, 'test', 'https://example.com/actor', 'example.com');
+
+ expect(result).toBe(false);
+ });
+
+ it('should exclude missing ID', () => {
+ const input = {
+ test: {
+ id: undefined,
+ },
+ };
+
+ const result = serviceUnderTest.sanitizeInlineObject(input, 'test', 'https://example.com/actor', 'example.com');
+
+ expect(result).toBe(false);
+ });
+
+ it('should exclude wrong host', () => {
+ const input = {
+ test: 'https://wrong.com/object',
+ };
+
+ const result = serviceUnderTest.sanitizeInlineObject(input, 'test', 'https://example.com/actor', 'example.com');
+
+ expect(result).toBe(false);
+ });
+
+ it('should exclude invalid URLs', () => {
+ const input = {
+ test: 'https://user@example.com/object',
+ };
+
+ const result = serviceUnderTest.sanitizeInlineObject(input, 'test', 'https://example.com/actor', 'example.com');
+
+ expect(result).toBe(false);
+ });
+
+ it('should accept string', () => {
+ const input = {
+ test: 'https://example.com/object',
+ };
+
+ const result = serviceUnderTest.sanitizeInlineObject(input, 'test', 'https://example.com/actor', 'example.com');
+
+ expect(result).toBe(true);
+ });
+
+ it('should accept array of string', () => {
+ const input = {
+ test: ['https://example.com/object'],
+ };
+
+ const result = serviceUnderTest.sanitizeInlineObject(input, 'test', 'https://example.com/actor', 'example.com');
+
+ expect(result).toBe(true);
+ });
+
+ it('should accept object', () => {
+ const input = {
+ test: {
+ id: 'https://example.com/object',
+ },
+ };
+
+ const result = serviceUnderTest.sanitizeInlineObject(input, 'test', 'https://example.com/actor', 'example.com');
+
+ expect(result).toBe(true);
+ });
+
+ it('should accept array of object', () => {
+ const input = {
+ test: [{
+ id: 'https://example.com/object',
+ }],
+ };
+
+ const result = serviceUnderTest.sanitizeInlineObject(input, 'test', 'https://example.com/actor', 'example.com');
+
+ expect(result).toBe(true);
+ });
+ });
});
diff --git a/packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts b/packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts
index b1f100698b..f7250600e3 100644
--- a/packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts
+++ b/packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts
@@ -303,9 +303,12 @@ describe(SkRateLimiterService, () => {
const i1 = await serviceUnderTest().limit(limit, actor); // 1 + 1 = 2
const i2 = await serviceUnderTest().limit(limit, actor); // 2 + 1 = 3
+ mockTimeService.now += 500; // 3 - 1 = 2 (at 1/2 time)
+ const i3 = await serviceUnderTest().limit(limit, actor);
expect(i1.blocked).toBeFalsy();
expect(i2.blocked).toBeTruthy();
+ expect(i3.blocked).toBeFalsy();
});
it('should set counter expiration', async () => {
@@ -563,11 +566,15 @@ describe(SkRateLimiterService, () => {
mockDefaultUserPolicies.rateLimitFactor = 0.5;
limitCounter = 1;
limitTimestamp = 0;
- mockTimeService.now += 500;
- const info = await serviceUnderTest().limit(limit, actor);
+ const i1 = await serviceUnderTest().limit(limit, actor);
+ const i2 = await serviceUnderTest().limit(limit, actor);
+ mockTimeService.now += 500;
+ const i3 = await serviceUnderTest().limit(limit, actor);
- expect(info.blocked).toBeFalsy();
+ expect(i1.blocked).toBeFalsy();
+ expect(i2.blocked).toBeTruthy();
+ expect(i3.blocked).toBeFalsy();
});
it('should set counter expiration', async () => {
@@ -738,12 +745,17 @@ describe(SkRateLimiterService, () => {
it('should scale limit by factor', async () => {
mockDefaultUserPolicies.rateLimitFactor = 0.5;
- limitCounter = 10;
+ limitCounter = 1;
limitTimestamp = 0;
- const info = await serviceUnderTest().limit(limit, actor); // 10 + 1 = 11
+ const i1 = await serviceUnderTest().limit(limit, actor);
+ const i2 = await serviceUnderTest().limit(limit, actor);
+ mockTimeService.now += 500;
+ const i3 = await serviceUnderTest().limit(limit, actor);
- expect(info.blocked).toBeTruthy();
+ expect(i1.blocked).toBeFalsy();
+ expect(i2.blocked).toBeTruthy();
+ expect(i3.blocked).toBeFalsy();
});
it('should set counter expiration', async () => {
@@ -932,13 +944,17 @@ describe(SkRateLimiterService, () => {
it('should scale limit and interval by factor', async () => {
mockDefaultUserPolicies.rateLimitFactor = 0.5;
- limitCounter = 5;
+ limitCounter = 19;
limitTimestamp = 0;
- mockTimeService.now += 500;
- const info = await serviceUnderTest().limit(limit, actor);
+ const i1 = await serviceUnderTest().limit(limit, actor);
+ const i2 = await serviceUnderTest().limit(limit, actor);
+ mockTimeService.now += 500;
+ const i3 = await serviceUnderTest().limit(limit, actor);
- expect(info.blocked).toBeFalsy();
+ expect(i1.blocked).toBeFalsy();
+ expect(i2.blocked).toBeTruthy();
+ expect(i3.blocked).toBeFalsy();
});
it('should set counter expiration', async () => {