+2
-2
.github/workflows/ci.yml
+2
-2
.github/workflows/ci.yml
+13
.prettierrc.json
+13
.prettierrc.json
···
1
+
{
2
+
"plugins": ["@trivago/prettier-plugin-sort-imports"],
3
+
"importOrder": [
4
+
"^node:",
5
+
"<THIRD_PARTY_MODULES>",
6
+
"^@atproto/(.*)$",
7
+
"^@skyware/(.*)$",
8
+
"^@clavata/(.*)$",
9
+
"^[./]"
10
+
],
11
+
"importOrderSeparation": false,
12
+
"importOrderSortSpecifiers": true
13
+
}
+1
-1
eslint.config.mjs
+1
-1
eslint.config.mjs
···
1
1
import eslint from "@eslint/js";
2
-
import tseslint from "typescript-eslint";
3
2
import stylistic from "@stylistic/eslint-plugin";
4
3
import prettier from "eslint-config-prettier";
5
4
import importPlugin from "eslint-plugin-import";
5
+
import tseslint from "typescript-eslint";
6
6
7
7
export default tseslint.config(
8
8
eslint.configs.recommended,
+4
-3
src/agent.ts
+4
-3
src/agent.ts
···
1
-
import { setGlobalDispatcher, Agent as Agent } from "undici";
1
+
import { Agent, setGlobalDispatcher } from "undici";
2
+
import { AtpAgent } from "@atproto/api";
3
+
import { BSKY_HANDLE, BSKY_PASSWORD, OZONE_PDS } from "./config.js";
4
+
2
5
setGlobalDispatcher(new Agent({ connect: { timeout: 20_000 } }));
3
-
import { BSKY_HANDLE, BSKY_PASSWORD, OZONE_PDS } from "./config.js";
4
-
import { AtpAgent } from "@atproto/api";
5
6
6
7
export const agent = new AtpAgent({
7
8
service: `https://${OZONE_PDS}`,
+3
-1
src/limits.ts
+3
-1
src/limits.ts
+5
-6
src/main.ts
+5
-6
src/main.ts
···
1
+
import fs from "node:fs";
1
2
import {
2
3
CommitCreateEvent,
3
4
CommitUpdateEvent,
4
5
IdentityEvent,
5
6
Jetstream,
6
7
} from "@skyware/jetstream";
7
-
import fs from "node:fs";
8
-
9
8
import {
10
9
CURSOR_UPDATE_INTERVAL,
11
10
FIREHOSE_URL,
···
14
13
} from "./config.js";
15
14
import { logger } from "./logger.js";
16
15
import { startMetricsServer } from "./metrics.js";
17
-
import { Post, LinkFeature, Handle } from "./types.js";
16
+
import { checkAccountAge } from "./rules/account/age.js";
17
+
import { checkFacetSpam } from "./rules/facets/facets.js";
18
+
import { checkHandle } from "./rules/handles/checkHandles.js";
18
19
import { checkPosts } from "./rules/posts/checkPosts.js";
19
-
import { checkHandle } from "./rules/handles/checkHandles.js";
20
20
import {
21
21
checkDescription,
22
22
checkDisplayName,
23
23
} from "./rules/profiles/checkProfiles.js";
24
-
import { checkFacetSpam } from "./rules/facets/facets.js";
25
-
import { checkAccountAge } from "./rules/account/age.js";
24
+
import { Handle, LinkFeature, Post } from "./types.js";
26
25
27
26
let cursor = 0;
28
27
let cursorUpdateInterval: NodeJS.Timeout;
-1
src/metrics.ts
-1
src/metrics.ts
+3
-3
src/rules/account/age.ts
+3
-3
src/rules/account/age.ts
···
1
1
import { agent, isLoggedIn } from "../../agent.js";
2
-
import { logger } from "../../logger.js";
3
-
import { createAccountLabel, checkAccountLabels } from "../../moderation.js";
4
-
import { ACCOUNT_AGE_CHECKS } from "./ageConstants.js";
5
2
import { PLC_URL } from "../../config.js";
6
3
import { GLOBAL_ALLOW } from "../../constants.js";
4
+
import { logger } from "../../logger.js";
5
+
import { checkAccountLabels, createAccountLabel } from "../../moderation.js";
6
+
import { ACCOUNT_AGE_CHECKS } from "./ageConstants.js";
7
7
8
8
interface InteractionContext {
9
9
// For replies
+2
-2
src/rules/account/countStarterPacks.ts
+2
-2
src/rules/account/countStarterPacks.ts
···
1
-
import { isLoggedIn, agent } from "../../agent.js";
2
-
import { logger } from "../../logger.js";
1
+
import { agent, isLoggedIn } from "../../agent.js";
3
2
import { limit } from "../../limits.js";
3
+
import { logger } from "../../logger.js";
4
4
import { createAccountLabel } from "../../moderation.js";
5
5
6
6
const ALLOWED_DIDS = ["did:plc:gpunjjgvlyb4racypz3yfiq4"];
+5
-6
src/rules/account/tests/age.test.ts
+5
-6
src/rules/account/tests/age.test.ts
···
1
-
import { describe, it, expect, vi, beforeEach } from "vitest";
1
+
import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
import { agent } from "../../../agent.js";
3
+
import { GLOBAL_ALLOW } from "../../../constants.js";
4
+
import { logger } from "../../../logger.js";
5
+
import { checkAccountLabels, createAccountLabel } from "../../../moderation.js";
2
6
import {
3
7
calculateAccountAge,
4
8
checkAccountAge,
···
34
38
35
39
// Mock fetch for DID document lookups
36
40
global.fetch = vi.fn();
37
-
38
-
import { agent } from "../../../agent.js";
39
-
import { logger } from "../../../logger.js";
40
-
import { createAccountLabel, checkAccountLabels } from "../../../moderation.js";
41
-
import { GLOBAL_ALLOW } from "../../../constants.js";
42
41
43
42
describe("Account Age Module", () => {
44
43
beforeEach(() => {
+5
-6
src/rules/account/tests/countStarterPacks.test.ts
+5
-6
src/rules/account/tests/countStarterPacks.test.ts
···
1
-
import { describe, it, expect, vi, beforeEach } from "vitest";
1
+
import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
import { agent } from "../../../agent.js";
3
+
import { limit } from "../../../limits.js";
4
+
import { logger } from "../../../logger.js";
5
+
import { createAccountLabel } from "../../../moderation.js";
2
6
import { countStarterPacks } from "../countStarterPacks.js";
3
7
4
8
// Mock dependencies
···
31
35
vi.mock("../../../limits.js", () => ({
32
36
limit: vi.fn((fn) => fn()),
33
37
}));
34
-
35
-
import { agent } from "../../../agent.js";
36
-
import { logger } from "../../../logger.js";
37
-
import { createAccountLabel } from "../../../moderation.js";
38
-
import { limit } from "../../../limits.js";
39
38
40
39
describe("countStarterPacks", () => {
41
40
beforeEach(() => {
+1
-1
src/rules/facets/facets.ts
+1
-1
src/rules/facets/facets.ts
+8
-9
src/rules/facets/tests/facets.test.ts
+8
-9
src/rules/facets/tests/facets.test.ts
···
1
-
import { describe, it, expect, vi, beforeEach } from "vitest";
1
+
import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
import { logger } from "../../../logger.js";
3
+
import { createAccountLabel } from "../../../moderation.js";
4
+
import { Facet } from "../../../types.js";
2
5
import {
3
-
checkFacetSpam,
4
-
FACET_SPAM_THRESHOLD,
6
+
FACET_SPAM_ALLOWLIST,
7
+
FACET_SPAM_COMMENT,
5
8
FACET_SPAM_LABEL,
6
-
FACET_SPAM_COMMENT,
7
-
FACET_SPAM_ALLOWLIST,
9
+
FACET_SPAM_THRESHOLD,
10
+
checkFacetSpam,
8
11
} from "../facets.js";
9
-
import { Facet } from "../../../types.js";
10
12
11
13
// Mock dependencies
12
14
vi.mock("../../../moderation.js", () => ({
···
20
22
error: vi.fn(),
21
23
},
22
24
}));
23
-
24
-
import { createAccountLabel } from "../../../moderation.js";
25
-
import { logger } from "../../../logger.js";
26
25
27
26
describe("checkFacetSpam", () => {
28
27
const TEST_DID = "did:plc:test123";
+3
-3
src/rules/handles/checkHandles.test.ts
+3
-3
src/rules/handles/checkHandles.test.ts
···
1
-
import { describe, it, expect, vi, beforeEach } from "vitest";
2
-
import { checkHandle } from "./checkHandles.js";
1
+
import { beforeEach, describe, expect, it, vi } from "vitest";
3
2
import {
4
-
createAccountReport,
5
3
createAccountComment,
6
4
createAccountLabel,
5
+
createAccountReport,
7
6
} from "../../moderation.js";
7
+
import { checkHandle } from "./checkHandles.js";
8
8
9
9
// Mock dependencies
10
10
vi.mock("../../moderation.js", () => ({
+3
-3
src/rules/handles/checkHandles.ts
+3
-3
src/rules/handles/checkHandles.ts
···
1
-
import { HANDLE_CHECKS } from "./constants.js";
1
+
import { GLOBAL_ALLOW } from "../../constants.js";
2
2
import { logger } from "../../logger.js";
3
3
import {
4
-
createAccountReport,
5
4
createAccountComment,
6
5
createAccountLabel,
6
+
createAccountReport,
7
7
} from "../../moderation.js";
8
-
import { GLOBAL_ALLOW } from "../../constants.js";
8
+
import { HANDLE_CHECKS } from "./constants.js";
9
9
10
10
export const checkHandle = async (
11
11
did: string,
+7
-7
src/rules/posts/checkPosts.ts
+7
-7
src/rules/posts/checkPosts.ts
···
1
-
import { LINK_SHORTENER, POST_CHECKS } from "./constants.js";
2
-
import { Post } from "../../types.js";
1
+
import { GLOBAL_ALLOW } from "../../constants.js";
3
2
import { logger } from "../../logger.js";
4
-
import { countStarterPacks } from "../account/countStarterPacks.js";
5
3
import {
6
-
createPostLabel,
4
+
createAccountComment,
7
5
createAccountReport,
8
-
createAccountComment,
6
+
createPostLabel,
9
7
createPostReport,
10
8
} from "../../moderation.js";
9
+
import { Post } from "../../types.js";
10
+
import { getFinalUrl } from "../../utils/getFinalUrl.js";
11
11
import { getLanguage } from "../../utils/getLanguage.js";
12
-
import { getFinalUrl } from "../../utils/getFinalUrl.js";
13
-
import { GLOBAL_ALLOW } from "../../constants.js";
12
+
import { countStarterPacks } from "../account/countStarterPacks.js";
13
+
import { LINK_SHORTENER, POST_CHECKS } from "./constants.js";
14
14
15
15
export const checkPosts = async (post: Post[]) => {
16
16
if (GLOBAL_ALLOW.includes(post[0].did)) {
+12
-13
src/rules/posts/tests/checkPosts.test.ts
+12
-13
src/rules/posts/tests/checkPosts.test.ts
···
1
-
import { describe, it, expect, vi, beforeEach } from "vitest";
2
-
import { checkPosts } from "../checkPosts.js";
1
+
import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
import { logger } from "../../../logger.js";
3
+
import {
4
+
createAccountComment,
5
+
createAccountReport,
6
+
createPostLabel,
7
+
createPostReport,
8
+
} from "../../../moderation.js";
3
9
import { Post } from "../../../types.js";
10
+
import { getFinalUrl } from "../../../utils/getFinalUrl.js";
11
+
import { getLanguage } from "../../../utils/getLanguage.js";
12
+
import { countStarterPacks } from "../../account/countStarterPacks.js";
13
+
import { checkPosts } from "../checkPosts.js";
4
14
5
15
// Mock dependencies
6
16
vi.mock("../constants.js", () => ({
···
88
98
vi.mock("../../../constants.js", () => ({
89
99
GLOBAL_ALLOW: ["did:plc:globalallow"],
90
100
}));
91
-
92
-
import { logger } from "../../../logger.js";
93
-
import { countStarterPacks } from "../../account/countStarterPacks.js";
94
-
import {
95
-
createPostLabel,
96
-
createAccountReport,
97
-
createAccountComment,
98
-
createPostReport,
99
-
} from "../../../moderation.js";
100
-
import { getLanguage } from "../../../utils/getLanguage.js";
101
-
import { getFinalUrl } from "../../../utils/getFinalUrl.js";
102
101
103
102
describe("checkPosts", () => {
104
103
beforeEach(() => {
+4
-4
src/rules/profiles/checkProfiles.ts
+4
-4
src/rules/profiles/checkProfiles.ts
···
1
-
import { PROFILE_CHECKS } from "../../rules/profiles/constants.js";
1
+
import { GLOBAL_ALLOW } from "../../constants.js";
2
2
import { logger } from "../../logger.js";
3
3
import {
4
-
createAccountReport,
5
-
createAccountLabel,
6
4
createAccountComment,
5
+
createAccountLabel,
6
+
createAccountReport,
7
7
} from "../../moderation.js";
8
+
import { PROFILE_CHECKS } from "../../rules/profiles/constants.js";
8
9
import { getLanguage } from "../../utils/getLanguage.js";
9
-
import { GLOBAL_ALLOW } from "../../constants.js";
10
10
11
11
export const checkDescription = async (
12
12
did: string,
+8
-9
src/rules/profiles/tests/checkProfiles.test.ts
+8
-9
src/rules/profiles/tests/checkProfiles.test.ts
···
1
-
import { describe, it, expect, vi, beforeEach } from "vitest";
1
+
import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
import { logger } from "../../../logger.js";
3
+
import {
4
+
createAccountComment,
5
+
createAccountLabel,
6
+
createAccountReport,
7
+
} from "../../../moderation.js";
8
+
import { getLanguage } from "../../../utils/getLanguage.js";
2
9
import { checkDescription, checkDisplayName } from "../checkProfiles.js";
3
10
4
11
// Mock dependencies
···
102
109
vi.mock("../../../constants.js", () => ({
103
110
GLOBAL_ALLOW: ["did:plc:globalallow"],
104
111
}));
105
-
106
-
import { logger } from "../../../logger.js";
107
-
import {
108
-
createAccountLabel,
109
-
createAccountReport,
110
-
createAccountComment,
111
-
} from "../../../moderation.js";
112
-
import { getLanguage } from "../../../utils/getLanguage.js";
113
112
114
113
describe("checkProfiles", () => {
115
114
beforeEach(() => {
+30
-28
src/tests/agent.test.ts
+30
-28
src/tests/agent.test.ts
···
1
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
1
+
import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
3
-
describe('Agent', () => {
3
+
describe("Agent", () => {
4
4
beforeEach(() => {
5
-
vi.resetModules()
6
-
})
5
+
vi.resetModules();
6
+
});
7
7
8
-
it('should create an agent and login', async () => {
8
+
it("should create an agent and login", async () => {
9
9
// Mock the config variables
10
-
vi.doMock('../config.js', () => ({
11
-
BSKY_HANDLE: 'test.bsky.social',
12
-
BSKY_PASSWORD: 'password',
13
-
OZONE_PDS: 'pds.test.com'
14
-
}))
10
+
vi.doMock("../config.js", () => ({
11
+
BSKY_HANDLE: "test.bsky.social",
12
+
BSKY_PASSWORD: "password",
13
+
OZONE_PDS: "pds.test.com",
14
+
}));
15
15
16
16
// Mock the AtpAgent
17
-
const mockLogin = vi.fn(() => Promise.resolve())
18
-
const mockConstructor = vi.fn()
19
-
vi.doMock('@atproto/api', () => ({
17
+
const mockLogin = vi.fn(() => Promise.resolve());
18
+
const mockConstructor = vi.fn();
19
+
vi.doMock("@atproto/api", () => ({
20
20
AtpAgent: class {
21
-
login = mockLogin
22
-
service: URL
21
+
login = mockLogin;
22
+
service: URL;
23
23
constructor(options: { service: string }) {
24
-
mockConstructor(options)
25
-
this.service = new URL(options.service)
24
+
mockConstructor(options);
25
+
this.service = new URL(options.service);
26
26
}
27
-
}
28
-
}))
27
+
},
28
+
}));
29
29
30
-
const { agent, login } = await import('../agent.js')
30
+
const { agent, login } = await import("../agent.js");
31
31
32
32
// Check that the agent was created with the correct service URL
33
-
expect(mockConstructor).toHaveBeenCalledWith({ service: 'https://pds.test.com' })
34
-
expect(agent.service.toString()).toBe('https://pds.test.com/')
33
+
expect(mockConstructor).toHaveBeenCalledWith({
34
+
service: "https://pds.test.com",
35
+
});
36
+
expect(agent.service.toString()).toBe("https://pds.test.com/");
35
37
36
38
// Check that the login function calls the mockLogin function
37
-
await login()
39
+
await login();
38
40
expect(mockLogin).toHaveBeenCalledWith({
39
-
identifier: 'test.bsky.social',
40
-
password: 'password'
41
-
})
42
-
})
43
-
})
41
+
identifier: "test.bsky.social",
42
+
password: "password",
43
+
});
44
+
});
45
+
});
+14
-14
src/tests/limits.test.ts
+14
-14
src/tests/limits.test.ts
···
1
-
import { describe, it, expect } from 'vitest'
2
-
import { limit } from '../limits.js'
1
+
import { describe, expect, it } from "vitest";
2
+
import { limit } from "../limits.js";
3
3
4
-
describe('Rate Limiter', () => {
5
-
it('should limit the rate of calls', async () => {
6
-
const calls = []
4
+
describe("Rate Limiter", () => {
5
+
it("should limit the rate of calls", async () => {
6
+
const calls = [];
7
7
for (let i = 0; i < 10; i++) {
8
-
calls.push(limit(() => Promise.resolve(Date.now())))
8
+
calls.push(limit(() => Promise.resolve(Date.now())));
9
9
}
10
10
11
-
const start = Date.now()
12
-
const results = await Promise.all(calls)
13
-
const end = Date.now()
11
+
const start = Date.now();
12
+
const results = await Promise.all(calls);
13
+
const end = Date.now();
14
14
15
15
// With a concurrency of 4, 10 calls should take at least 2 intervals.
16
16
// However, the interval is 30 seconds, so this test would be very slow.
17
17
// Instead, we'll just check that the calls were successful and returned a timestamp.
18
-
expect(results.length).toBe(10)
18
+
expect(results.length).toBe(10);
19
19
for (const result of results) {
20
-
expect(typeof result).toBe('number')
20
+
expect(typeof result).toBe("number");
21
21
}
22
22
// A better test would be to mock the timer and advance it, but that's more complex.
23
23
// For now, we'll just check that the time taken is greater than 0.
24
-
expect(end - start).toBeGreaterThanOrEqual(0)
25
-
}, 40000) // Increase timeout for this test
26
-
})
24
+
expect(end - start).toBeGreaterThanOrEqual(0);
25
+
}, 40000); // Increase timeout for this test
26
+
});
+16
-16
src/tests/metrics.test.ts
+16
-16
src/tests/metrics.test.ts
···
1
-
import { describe, it, expect } from 'vitest'
2
-
import request from 'supertest'
3
-
import { startMetricsServer } from '../metrics.js'
4
-
import { Server } from 'http'
1
+
import { Server } from "http";
2
+
import request from "supertest";
3
+
import { describe, expect, it } from "vitest";
4
+
import { startMetricsServer } from "../metrics.js";
5
5
6
-
describe('Metrics Server', () => {
7
-
let server: Server
6
+
describe("Metrics Server", () => {
7
+
let server: Server;
8
8
9
9
afterEach(() => {
10
10
if (server) {
11
-
server.close()
11
+
server.close();
12
12
}
13
-
})
13
+
});
14
14
15
-
it('should return metrics on /metrics endpoint', async () => {
16
-
server = startMetricsServer(0)
17
-
const response = await request(server).get('/metrics')
18
-
expect(response.status).toBe(200)
19
-
expect(response.headers['content-type']).toContain('text/plain')
20
-
expect(response.text).toContain('process_cpu_user_seconds_total')
21
-
})
22
-
})
15
+
it("should return metrics on /metrics endpoint", async () => {
16
+
server = startMetricsServer(0);
17
+
const response = await request(server).get("/metrics");
18
+
expect(response.status).toBe(200);
19
+
expect(response.headers["content-type"]).toContain("text/plain");
20
+
expect(response.text).toContain("process_cpu_user_seconds_total");
21
+
});
22
+
});
+3
-4
src/tests/moderation.test.ts
+3
-4
src/tests/moderation.test.ts
···
1
-
import { describe, it, expect, vi, beforeEach } from "vitest";
1
+
import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
import { agent } from "../agent.js";
3
+
import { logger } from "../logger.js";
2
4
import { checkAccountLabels } from "../moderation.js";
3
5
4
6
// Mock dependencies
···
31
33
vi.mock("../limits.js", () => ({
32
34
limit: vi.fn((fn) => fn()),
33
35
}));
34
-
35
-
import { agent } from "../agent.js";
36
-
import { logger } from "../logger.js";
37
36
38
37
describe("checkAccountLabels", () => {
39
38
beforeEach(() => {
+1
-1
src/utils/getFinalUrl.test.ts
+1
-1
src/utils/getFinalUrl.test.ts
+1
-1
src/utils/getLanguage.test.ts
+1
-1
src/utils/getLanguage.test.ts
+36
-36
src/utils/homoglyph.test.ts
+36
-36
src/utils/homoglyph.test.ts
···
1
-
import { describe, it, expect } from 'vitest'
2
-
import { normalizeUnicode } from './normalizeUnicode.js'
1
+
import { describe, expect, it } from "vitest";
2
+
import { normalizeUnicode } from "./normalizeUnicode.js";
3
3
4
-
describe('normalizeUnicode with Homoglyphs', () => {
5
-
it('should replace basic homoglyphs', () => {
6
-
expect(normalizeUnicode('h3ll0')).toBe('hello')
7
-
expect(normalizeUnicode('w0rld')).toBe('world')
8
-
expect(normalizeUnicode('p@ssword')).toBe('password')
9
-
expect(normalizeUnicode('1nternet')).toBe('internet')
10
-
})
4
+
describe("normalizeUnicode with Homoglyphs", () => {
5
+
it("should replace basic homoglyphs", () => {
6
+
expect(normalizeUnicode("h3ll0")).toBe("hello");
7
+
expect(normalizeUnicode("w0rld")).toBe("world");
8
+
expect(normalizeUnicode("p@ssword")).toBe("password");
9
+
expect(normalizeUnicode("1nternet")).toBe("internet");
10
+
});
11
11
12
-
it('should replace Cyrillic homoglyphs', () => {
13
-
expect(normalizeUnicode('аpple')).toBe('apple') // 'а' is Cyrillic
14
-
expect(normalizeUnicode('еlеphant')).toBe('elephant') // 'е' is Cyrillic
15
-
expect(normalizeUnicode('оrange')).toBe('orange') // 'о' is Cyrillic
16
-
})
12
+
it("should replace Cyrillic homoglyphs", () => {
13
+
expect(normalizeUnicode("аpple")).toBe("apple"); // 'а' is Cyrillic
14
+
expect(normalizeUnicode("еlеphant")).toBe("elephant"); // 'е' is Cyrillic
15
+
expect(normalizeUnicode("оrange")).toBe("orange"); // 'о' is Cyrillic
16
+
});
17
17
18
-
it('should handle a mix of homoglyphs and regular characters', () => {
19
-
expect(normalizeUnicode('p@y-pаl')).toBe('pay-pal')
20
-
expect(normalizeUnicode('g00gl3')).toBe('google')
21
-
})
18
+
it("should handle a mix of homoglyphs and regular characters", () => {
19
+
expect(normalizeUnicode("p@y-pаl")).toBe("pay-pal");
20
+
expect(normalizeUnicode("g00gl3")).toBe("google");
21
+
});
22
22
23
-
it('should handle accented and other Unicode characters', () => {
24
-
expect(normalizeUnicode('déjà vu')).toBe('deja vu')
25
-
expect(normalizeUnicode('naïve')).toBe('naive')
26
-
expect(normalizeUnicode('façade')).toBe('facade')
27
-
})
23
+
it("should handle accented and other Unicode characters", () => {
24
+
expect(normalizeUnicode("déjà vu")).toBe("deja vu");
25
+
expect(normalizeUnicode("naïve")).toBe("naive");
26
+
expect(normalizeUnicode("façade")).toBe("facade");
27
+
});
28
28
29
-
it('should handle complex strings with multiple homoglyph types', () => {
30
-
const complexString = 'p@sswоrd123-еxаmple' // with Cyrillic 'о', 'а', 'е'
31
-
const expectedString = 'passwordize-example'
32
-
expect(normalizeUnicode(complexString)).toBe(expectedString)
33
-
})
29
+
it("should handle complex strings with multiple homoglyph types", () => {
30
+
const complexString = "p@sswоrd123-еxаmple"; // with Cyrillic 'о', 'а', 'е'
31
+
const expectedString = "passwordize-example";
32
+
expect(normalizeUnicode(complexString)).toBe(expectedString);
33
+
});
34
34
35
-
it('should not affect strings with no homoglyphs', () => {
36
-
const normalString = 'hello world'
37
-
expect(normalizeUnicode(normalString)).toBe(normalString)
38
-
})
35
+
it("should not affect strings with no homoglyphs", () => {
36
+
const normalString = "hello world";
37
+
expect(normalizeUnicode(normalString)).toBe(normalString);
38
+
});
39
39
40
-
it('should handle an empty string', () => {
41
-
expect(normalizeUnicode('')).toBe('')
42
-
})
43
-
})
40
+
it("should handle an empty string", () => {
41
+
expect(normalizeUnicode("")).toBe("");
42
+
});
43
+
});
+1
-1
src/utils/homoglyphs.ts
+1
-1
src/utils/homoglyphs.ts
+1
-1
src/utils/normalizeUnicode.test.ts
+1
-1
src/utils/normalizeUnicode.test.ts