@20syldev/api 4.8.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +93 -55
  2. package/dist/config/versions.js +16 -0
  3. package/dist/config/versions.js.map +1 -1
  4. package/dist/constants.js +5 -0
  5. package/dist/constants.js.map +1 -1
  6. package/dist/modules/v5/chart.js +379 -0
  7. package/dist/modules/v5/chart.js.map +1 -0
  8. package/dist/modules/v5/evaluate.js +281 -0
  9. package/dist/modules/v5/evaluate.js.map +1 -0
  10. package/dist/modules/v5/matrix.js +238 -0
  11. package/dist/modules/v5/matrix.js.map +1 -0
  12. package/dist/modules/v5.js +5 -0
  13. package/dist/modules/v5.js.map +1 -0
  14. package/dist/routes/get.js +191 -162
  15. package/dist/routes/get.js.map +1 -1
  16. package/dist/routes/post.js +69 -0
  17. package/dist/routes/post.js.map +1 -1
  18. package/package.json +4 -2
  19. package/recap.md +98 -0
  20. package/.github/FUNDING.yml +0 -1
  21. package/.prettierrc +0 -7
  22. package/eslint.config.js +0 -24
  23. package/robots.txt +0 -74
  24. package/src/app.ts +0 -53
  25. package/src/config/env.ts +0 -87
  26. package/src/config/plans.ts +0 -61
  27. package/src/config/versions.ts +0 -121
  28. package/src/constants.ts +0 -61
  29. package/src/favicon.ico +0 -0
  30. package/src/middleware/cors.ts +0 -23
  31. package/src/middleware/error.ts +0 -16
  32. package/src/middleware/json.ts +0 -9
  33. package/src/middleware/logger.ts +0 -32
  34. package/src/middleware/ratelimit.ts +0 -89
  35. package/src/middleware/version.ts +0 -40
  36. package/src/modules/v3/algorithms.js +0 -218
  37. package/src/modules/v3/captcha.js +0 -60
  38. package/src/modules/v3/chat.js +0 -111
  39. package/src/modules/v3/color.js +0 -60
  40. package/src/modules/v3/convert.js +0 -39
  41. package/src/modules/v3/domain.js +0 -86
  42. package/src/modules/v3/hash.js +0 -16
  43. package/src/modules/v3/hyperplanning.js +0 -56
  44. package/src/modules/v3/levenshtein.js +0 -46
  45. package/src/modules/v3/personal.js +0 -139
  46. package/src/modules/v3/qrcode.js +0 -20
  47. package/src/modules/v3/tic_tac_toe.js +0 -242
  48. package/src/modules/v3/time.js +0 -98
  49. package/src/modules/v3/token.js +0 -54
  50. package/src/modules/v3/username.js +0 -94
  51. package/src/modules/v3/utils.js +0 -77
  52. package/src/modules/v3.js +0 -15
  53. package/src/modules/v4/address.ts +0 -412
  54. package/src/modules/v4/agent.ts +0 -104
  55. package/src/modules/v4/algorithms.ts +0 -276
  56. package/src/modules/v4/captcha.ts +0 -114
  57. package/src/modules/v4/chat.ts +0 -130
  58. package/src/modules/v4/color.ts +0 -75
  59. package/src/modules/v4/convert.ts +0 -127
  60. package/src/modules/v4/cron.ts +0 -221
  61. package/src/modules/v4/dice.ts +0 -46
  62. package/src/modules/v4/domain.ts +0 -86
  63. package/src/modules/v4/encode.ts +0 -241
  64. package/src/modules/v4/geo.ts +0 -64
  65. package/src/modules/v4/hash.ts +0 -32
  66. package/src/modules/v4/hyperplanning.ts +0 -93
  67. package/src/modules/v4/ip.ts +0 -134
  68. package/src/modules/v4/levenshtein.ts +0 -46
  69. package/src/modules/v4/palette.ts +0 -71
  70. package/src/modules/v4/password.ts +0 -379
  71. package/src/modules/v4/personal.ts +0 -140
  72. package/src/modules/v4/placeholder.ts +0 -197
  73. package/src/modules/v4/qrcode.ts +0 -121
  74. package/src/modules/v4/regex.ts +0 -67
  75. package/src/modules/v4/statistics.ts +0 -62
  76. package/src/modules/v4/text.ts +0 -330
  77. package/src/modules/v4/tic_tac_toe.ts +0 -264
  78. package/src/modules/v4/time.ts +0 -149
  79. package/src/modules/v4/token.ts +0 -53
  80. package/src/modules/v4/username.ts +0 -94
  81. package/src/modules/v4/validate.ts +0 -76
  82. package/src/modules/v4.ts +0 -29
  83. package/src/routes/delete.ts +0 -72
  84. package/src/routes/get.ts +0 -856
  85. package/src/routes/index.ts +0 -59
  86. package/src/routes/patch.ts +0 -45
  87. package/src/routes/post.ts +0 -209
  88. package/src/storage/index.ts +0 -16
  89. package/src/types/express.d.ts +0 -17
  90. package/src/types/storage.ts +0 -48
  91. package/src/utils/colors.ts +0 -91
  92. package/src/utils/helpers.ts +0 -90
  93. package/src/utils/response.ts +0 -28
  94. package/tests/integration/api.test.ts +0 -772
  95. package/tests/tsconfig.json +0 -3
  96. package/tests/unit/address.test.ts +0 -84
  97. package/tests/unit/agent.test.ts +0 -113
  98. package/tests/unit/algorithms.test.ts +0 -121
  99. package/tests/unit/captcha.test.ts +0 -62
  100. package/tests/unit/chat.test.ts +0 -54
  101. package/tests/unit/color.test.ts +0 -54
  102. package/tests/unit/convert.test.ts +0 -68
  103. package/tests/unit/cron.test.ts +0 -110
  104. package/tests/unit/dice.test.ts +0 -58
  105. package/tests/unit/domain.test.ts +0 -48
  106. package/tests/unit/encode.test.ts +0 -109
  107. package/tests/unit/geo.test.ts +0 -46
  108. package/tests/unit/hash.test.ts +0 -45
  109. package/tests/unit/hyperplanning.test.ts +0 -97
  110. package/tests/unit/ip.test.ts +0 -140
  111. package/tests/unit/levenshtein.test.ts +0 -35
  112. package/tests/unit/palette.test.ts +0 -54
  113. package/tests/unit/password.test.ts +0 -86
  114. package/tests/unit/personal.test.ts +0 -66
  115. package/tests/unit/placeholder.test.ts +0 -58
  116. package/tests/unit/qrcode.test.ts +0 -83
  117. package/tests/unit/regex.test.ts +0 -95
  118. package/tests/unit/statistics.test.ts +0 -58
  119. package/tests/unit/text.test.ts +0 -108
  120. package/tests/unit/tic_tac_toe.test.ts +0 -56
  121. package/tests/unit/time.test.ts +0 -115
  122. package/tests/unit/token.test.ts +0 -62
  123. package/tests/unit/username.test.ts +0 -35
  124. package/tests/unit/validate.test.ts +0 -72
  125. package/tsconfig.json +0 -17
  126. package/tsconfig.test.json +0 -9
@@ -1,58 +0,0 @@
1
- import { strict as assert } from 'node:assert';
2
- import { describe, test } from 'node:test';
3
-
4
- import dice from '../../src/modules/v4/dice.js';
5
-
6
- describe('dice', () => {
7
- test('parses 2d6+3 notation', () => {
8
- const result = dice('2d6+3');
9
- assert.equal(result.count, 2);
10
- assert.equal(result.sides, 6);
11
- assert.equal(result.modifier, 3);
12
- assert.equal(result.results.length, 2);
13
- });
14
-
15
- test('total includes positive modifier', () => {
16
- const result = dice('1d2+10');
17
- assert.equal(result.modifier, 10);
18
- assert.ok(result.total === 11 || result.total === 12);
19
- });
20
-
21
- test('negative modifier', () => {
22
- const result = dice('1d2-5');
23
- assert.equal(result.modifier, -5);
24
- assert.ok(result.total === -4 || result.total === -3);
25
- });
26
-
27
- test('default count of 1 when omitted', () => {
28
- const result = dice('d20');
29
- assert.equal(result.count, 1);
30
- assert.equal(result.sides, 20);
31
- });
32
-
33
- test('all results are within 1..sides', () => {
34
- for (let i = 0; i < 50; i++) {
35
- const result = dice('5d10');
36
- for (const r of result.results) {
37
- assert.ok(r >= 1 && r <= 10, `result ${r} out of range`);
38
- }
39
- }
40
- });
41
-
42
- test('handles space instead of +', () => {
43
- const result = dice('2d6 3');
44
- assert.equal(result.modifier, 3);
45
- });
46
-
47
- test('throws on invalid notation', () => {
48
- assert.throws(() => dice('2x6'), /Invalid notation/);
49
- });
50
-
51
- test('throws on too many dice', () => {
52
- assert.throws(() => dice('200d6'), /between 1 and 100/);
53
- });
54
-
55
- test('throws on invalid sides', () => {
56
- assert.throws(() => dice('2d1'), /between 2 and 1000/);
57
- });
58
- });
@@ -1,48 +0,0 @@
1
- import { strict as assert } from 'node:assert';
2
- import { describe, test } from 'node:test';
3
-
4
- import domain from '../../src/modules/v4/domain.js';
5
-
6
- describe('domain', () => {
7
- test('returns expected fields', () => {
8
- const result = domain();
9
- assert.ok('domain' in result);
10
- assert.ok('full_domain' in result);
11
- assert.ok('ip_address' in result);
12
- assert.ok('dns_servers' in result);
13
- assert.ok('country' in result);
14
- });
15
-
16
- test('domain has TLD', () => {
17
- const result = domain();
18
- assert.match(
19
- result.domain as string,
20
- /\.(com|fr|eu|dev|net|org|io|tech|biz|info|co|app|store|online|shop|tv)$/,
21
- );
22
- });
23
-
24
- test('full_domain starts with subdomain', () => {
25
- const result = domain();
26
- const full = result.full_domain as string;
27
- const dom = result.domain as string;
28
- assert.ok(full.endsWith(dom));
29
- });
30
-
31
- test('ip_address is non-empty array', () => {
32
- const result = domain();
33
- const ips = result.ip_address as string[];
34
- assert.ok(Array.isArray(ips));
35
- assert.ok(ips.length >= 1);
36
- });
37
-
38
- test('seo_score in range 0..99', () => {
39
- const result = domain();
40
- const score = result.seo_score as number;
41
- assert.ok(score >= 0 && score < 100);
42
- });
43
-
44
- test('ssl_certified is boolean', () => {
45
- const result = domain();
46
- assert.equal(typeof result.ssl_certified, 'boolean');
47
- });
48
- });
@@ -1,109 +0,0 @@
1
- import { strict as assert } from 'node:assert';
2
- import { describe, test } from 'node:test';
3
-
4
- import {
5
- base64decode,
6
- base64encode,
7
- binary,
8
- caesar,
9
- morse,
10
- rot13,
11
- unbinary,
12
- unmorse,
13
- urldecode,
14
- urlencode,
15
- } from '../../src/modules/v4/encode.js';
16
-
17
- describe('encode', () => {
18
- describe('base64', () => {
19
- test('encodes ASCII text', () => {
20
- assert.equal(base64encode('hello'), 'aGVsbG8=');
21
- });
22
-
23
- test('round-trip preserves UTF-8', () => {
24
- assert.equal(base64decode(base64encode('héllo wörld')), 'héllo wörld');
25
- });
26
-
27
- test('decode rejects invalid base64', () => {
28
- assert.throws(() => base64decode('not!valid'), /Invalid Base64/);
29
- });
30
- });
31
-
32
- describe('url', () => {
33
- test('encodes special chars', () => {
34
- assert.equal(urlencode('a b&c=d'), 'a%20b%26c%3Dd');
35
- });
36
-
37
- test('round-trip', () => {
38
- assert.equal(urldecode(urlencode('hello world?')), 'hello world?');
39
- });
40
-
41
- test('decode rejects malformed sequence', () => {
42
- assert.throws(() => urldecode('%ZZ'), /Invalid URL/);
43
- });
44
- });
45
-
46
- describe('morse', () => {
47
- test('encodes SOS', () => {
48
- assert.equal(morse('SOS'), '... --- ...');
49
- });
50
-
51
- test('round-trip with words', () => {
52
- assert.equal(unmorse(morse('HELLO WORLD')), 'HELLO WORLD');
53
- });
54
-
55
- test('throws on unsupported char', () => {
56
- assert.throws(() => morse('hello#'), /Unsupported character/);
57
- });
58
- });
59
-
60
- describe('rot13', () => {
61
- test('shifts letters by 13', () => {
62
- assert.equal(rot13('Hello'), 'Uryyb');
63
- });
64
-
65
- test('is its own inverse', () => {
66
- assert.equal(rot13(rot13('The Quick Brown Fox')), 'The Quick Brown Fox');
67
- });
68
-
69
- test('preserves non-alpha chars', () => {
70
- assert.equal(rot13('abc 123!'), 'nop 123!');
71
- });
72
- });
73
-
74
- describe('caesar', () => {
75
- test('shift 3 forward', () => {
76
- assert.equal(caesar('abc', '3'), 'def');
77
- });
78
-
79
- test('shift wraps around alphabet', () => {
80
- assert.equal(caesar('xyz', '3'), 'abc');
81
- });
82
-
83
- test('negative shift', () => {
84
- assert.equal(caesar('def', '-3'), 'abc');
85
- });
86
-
87
- test('throws on non-numeric shift', () => {
88
- assert.throws(() => caesar('abc', 'abc'), /Shift must be a number/);
89
- });
90
- });
91
-
92
- describe('binary', () => {
93
- test('encodes A as 01000001', () => {
94
- assert.equal(binary('A'), '01000001');
95
- });
96
-
97
- test('round-trip', () => {
98
- assert.equal(unbinary(binary('Hello!')), 'Hello!');
99
- });
100
-
101
- test('rejects malformed binary', () => {
102
- assert.throws(() => unbinary('012'), /Invalid binary/);
103
- });
104
-
105
- test('rejects wrong group size', () => {
106
- assert.throws(() => unbinary('0100'), /Each binary group must contain 8 bits/);
107
- });
108
- });
109
- });
@@ -1,46 +0,0 @@
1
- import { strict as assert } from 'node:assert';
2
- import { describe, test } from 'node:test';
3
-
4
- import geo from '../../src/modules/v4/geo.js';
5
-
6
- describe('geo', () => {
7
- test('Paris to NYC distance ~5837 km', () => {
8
- const result = geo('48.8566', '2.3522', '40.7128', '-74.006');
9
- assert.ok(Math.abs(result.distance.km - 5837) < 50, `expected ~5837, got ${result.distance.km}`);
10
- });
11
-
12
- test('same point returns 0 km', () => {
13
- const result = geo('48.8566', '2.3522', '48.8566', '2.3522');
14
- assert.equal(result.distance.km, 0);
15
- });
16
-
17
- test('miles and nautical miles match km conversion', () => {
18
- const result = geo('0', '0', '0', '90');
19
- assert.ok(Math.abs(result.distance.miles - result.distance.km * 0.621371) < 0.01);
20
- assert.ok(Math.abs(result.distance.nauticalMiles - result.distance.km * 0.539957) < 0.01);
21
- });
22
-
23
- test('bearing N for due north', () => {
24
- const result = geo('0', '0', '10', '0');
25
- assert.equal(result.bearing.degrees, 0);
26
- assert.equal(result.bearing.cardinal, 'N');
27
- });
28
-
29
- test('bearing E for due east', () => {
30
- const result = geo('0', '0', '0', '10');
31
- assert.equal(result.bearing.degrees, 90);
32
- assert.equal(result.bearing.cardinal, 'E');
33
- });
34
-
35
- test('throws on invalid latitude', () => {
36
- assert.throws(() => geo('100', '0', '0', '0'), /lat1 must be between/);
37
- });
38
-
39
- test('throws on invalid longitude', () => {
40
- assert.throws(() => geo('0', '200', '0', '0'), /lon1 must be between/);
41
- });
42
-
43
- test('throws on non-numeric coords', () => {
44
- assert.throws(() => geo('abc', '0', '0', '0'), /must be a number/);
45
- });
46
- });
@@ -1,45 +0,0 @@
1
- import { strict as assert } from 'node:assert';
2
- import { describe, test } from 'node:test';
3
-
4
- import hash from '../../src/modules/v4/hash.js';
5
-
6
- describe('hash', () => {
7
- test('sha256 of "hello"', () => {
8
- const result = hash('hello', 'sha256');
9
- assert.deepEqual(result, {
10
- method: 'sha256',
11
- hash: '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
12
- encoding: 'hex',
13
- });
14
- });
15
-
16
- test('md5 of "hello"', () => {
17
- const result = hash('hello', 'md5');
18
- assert.equal(result.hash, '5d41402abc4b2a76b9719d911017c592');
19
- assert.equal(result.encoding, 'hex');
20
- });
21
-
22
- test('base64 encoding', () => {
23
- const result = hash('hello', 'sha256', 'base64');
24
- assert.equal(result.encoding, 'base64');
25
- assert.ok(result.hash.length > 0);
26
- });
27
-
28
- test('throws on empty text', () => {
29
- assert.throws(() => hash('', 'sha256'), /Text is required/);
30
- });
31
-
32
- test('throws on unsupported method', () => {
33
- assert.throws(() => hash('hello', 'fakehash'), /Unsupported method/);
34
- });
35
-
36
- test('throws on invalid encoding', () => {
37
- assert.throws(() => hash('hello', 'sha256', 'utf8'), /Encoding must be/);
38
- });
39
-
40
- test('same input + method = same hash (deterministic)', () => {
41
- const a = hash('test', 'sha256');
42
- const b = hash('test', 'sha256');
43
- assert.deepEqual(a, b);
44
- });
45
- });
@@ -1,97 +0,0 @@
1
- import { strict as assert } from 'node:assert';
2
- import { after, before, describe, test } from 'node:test';
3
-
4
- import hyperplanning from '../../src/modules/v4/hyperplanning.js';
5
-
6
- const ORIGINAL_FETCH = globalThis.fetch;
7
-
8
- const sampleIcs = `BEGIN:VCALENDAR
9
- VERSION:2.0
10
- PRODID:-//Test//Test//EN
11
- BEGIN:VEVENT
12
- UID:event-1@test
13
- DTSTAMP:20990101T000000Z
14
- DTSTART:20990101T080000Z
15
- DTEND:20990101T090000Z
16
- SUMMARY:Mathematics - CM
17
- DESCRIPTION:Matière : Mathematics\\nEnseignant : Mr Smith\\nPromotions : G1, G2\\nSalle : Room 101
18
- END:VEVENT
19
- END:VCALENDAR`;
20
-
21
- function mockFetch(body: string, ok = true, contentType = 'text/calendar'): void {
22
- globalThis.fetch = (async () => ({
23
- ok,
24
- headers: { get: (h: string) => (h.toLowerCase() === 'content-type' ? contentType : null) },
25
- text: async () => body,
26
- })) as unknown as typeof fetch;
27
- }
28
-
29
- describe('hyperplanning', () => {
30
- after(() => {
31
- globalThis.fetch = ORIGINAL_FETCH;
32
- });
33
-
34
- test('parses valid ICS with default detail', async () => {
35
- mockFetch(sampleIcs);
36
- const events = await hyperplanning('https://fake.test/cal.ics');
37
- assert.equal(events.length, 1);
38
- assert.equal(events[0]!.summary, 'Mathematics - CM');
39
- assert.ok(events[0]!.start);
40
- assert.ok(events[0]!.end);
41
- });
42
-
43
- test('detail=list returns split summary', async () => {
44
- mockFetch(sampleIcs);
45
- const events = await hyperplanning('https://fake.test/cal.ics', 'list');
46
- assert.deepEqual(events[0]!.summary, ['Mathematics', 'CM']);
47
- });
48
-
49
- test('detail=full extracts subject/teacher/classes', async () => {
50
- mockFetch(sampleIcs);
51
- const events = await hyperplanning('https://fake.test/cal.ics', 'full');
52
- const e = events[0]!;
53
- assert.equal(e.subject, 'Mathematics');
54
- assert.equal(e.teacher, 'Mr Smith');
55
- assert.deepEqual(e.classes, ['G1', 'G2']);
56
- });
57
-
58
- test('throws on non-OK response', async () => {
59
- mockFetch('', false);
60
- await assert.rejects(() => hyperplanning('https://fake.test/cal.ics'), /Invalid ICS/);
61
- });
62
-
63
- test('throws on wrong content-type', async () => {
64
- mockFetch(sampleIcs, true, 'text/html');
65
- await assert.rejects(() => hyperplanning('https://fake.test/cal.ics'), /Invalid ICS/);
66
- });
67
-
68
- test('filters out past events', async () => {
69
- const pastIcs = sampleIcs.replaceAll('20990101', '19990101');
70
- mockFetch(pastIcs);
71
- const events = await hyperplanning('https://fake.test/cal.ics');
72
- assert.equal(events.length, 0);
73
- });
74
-
75
- test('throws on HTTP URL', async () => {
76
- await assert.rejects(() => hyperplanning('http://fake.test/cal.ics'), /Only HTTPS/);
77
- });
78
-
79
- test('throws on private IP', async () => {
80
- await assert.rejects(() => hyperplanning('https://127.0.0.1/cal.ics'), /private/);
81
- await assert.rejects(() => hyperplanning('https://192.168.1.1/cal.ics'), /private/);
82
- await assert.rejects(() => hyperplanning('https://10.0.0.1/cal.ics'), /private/);
83
- await assert.rejects(() => hyperplanning('https://169.254.169.254/latest'), /private/);
84
- });
85
-
86
- test('throws on localhost', async () => {
87
- await assert.rejects(() => hyperplanning('https://localhost/cal.ics'), /private/);
88
- });
89
-
90
- test('throws on invalid URL', async () => {
91
- await assert.rejects(() => hyperplanning('not-a-url'), /Invalid URL/);
92
- });
93
- });
94
-
95
- before(() => {
96
- globalThis.fetch = ORIGINAL_FETCH;
97
- });
@@ -1,140 +0,0 @@
1
- import { strict as assert } from 'node:assert';
2
- import { describe, test } from 'node:test';
3
-
4
- import ip from '../../src/modules/v4/ip.js';
5
-
6
- describe('ip', () => {
7
- describe('IPv4 — type detection', () => {
8
- test('public IP returns public type and class A', () => {
9
- const result = ip('8.8.8.8');
10
- assert.equal(result.version, 'IPv4');
11
- assert.equal(result.type, 'public');
12
- assert.equal(result.class, 'A');
13
- });
14
-
15
- test('private 10.x.x.x', () => {
16
- const result = ip('10.0.0.1');
17
- assert.equal(result.type, 'private');
18
- assert.equal(result.range, '10.0.0.0/8');
19
- });
20
-
21
- test('private 172.16-31.x.x', () => {
22
- const result = ip('172.16.0.1');
23
- assert.equal(result.type, 'private');
24
- assert.equal(result.range, '172.16.0.0/12');
25
- });
26
-
27
- test('private 192.168.x.x', () => {
28
- const result = ip('192.168.1.1');
29
- assert.equal(result.type, 'private');
30
- assert.equal(result.class, 'C');
31
- assert.equal(result.range, '192.168.0.0/16');
32
- });
33
-
34
- test('loopback 127.0.0.1', () => {
35
- const result = ip('127.0.0.1');
36
- assert.equal(result.type, 'loopback');
37
- assert.equal(result.range, '127.0.0.0/8');
38
- });
39
-
40
- test('link-local 169.254.x.x', () => {
41
- const result = ip('169.254.1.1');
42
- assert.equal(result.type, 'link-local');
43
- });
44
-
45
- test('multicast 224.x.x.x', () => {
46
- const result = ip('224.0.0.1');
47
- assert.equal(result.type, 'multicast');
48
- });
49
-
50
- test('broadcast 255.255.255.255', () => {
51
- const result = ip('255.255.255.255');
52
- assert.equal(result.type, 'broadcast');
53
- assert.equal(result.class, 'E');
54
- });
55
- });
56
-
57
- describe('IPv4 — representations', () => {
58
- test('binary of 192.168.1.1', () => {
59
- const result = ip('192.168.1.1');
60
- assert.equal(result.binary, '11000000.10101000.00000001.00000001');
61
- });
62
-
63
- test('decimal of 192.168.1.1', () => {
64
- const result = ip('192.168.1.1');
65
- assert.equal(result.decimal, 3232235777);
66
- });
67
-
68
- test('decimal of 8.8.8.8', () => {
69
- const result = ip('8.8.8.8');
70
- assert.equal(result.decimal, 134744072);
71
- });
72
-
73
- test('reverse DNS of 192.168.1.1', () => {
74
- const result = ip('192.168.1.1');
75
- assert.equal(result.reverse, '1.1.168.192.in-addr.arpa');
76
- });
77
-
78
- test('class B for 128.x', () => {
79
- assert.equal(ip('128.0.0.1').class, 'B');
80
- });
81
-
82
- test('class C for 192.x (non-private)', () => {
83
- assert.equal(ip('200.0.0.1').class, 'C');
84
- });
85
- });
86
-
87
- describe('IPv6', () => {
88
- test('loopback ::1', () => {
89
- const result = ip('::1');
90
- assert.equal(result.version, 'IPv6');
91
- assert.equal(result.type, 'loopback');
92
- assert.equal(result.range, '::1/128');
93
- });
94
-
95
- test('link-local fe80::1', () => {
96
- const result = ip('fe80::1');
97
- assert.equal(result.type, 'link-local');
98
- assert.equal(result.range, 'fe80::/10');
99
- });
100
-
101
- test('unique-local fc00::1', () => {
102
- const result = ip('fc00::1');
103
- assert.equal(result.type, 'private');
104
- assert.equal(result.range, 'fc00::/7');
105
- });
106
-
107
- test('multicast ff02::1', () => {
108
- const result = ip('ff02::1');
109
- assert.equal(result.type, 'multicast');
110
- });
111
-
112
- test(':: expansion produces 8 groups', () => {
113
- const result = ip('::1');
114
- assert.equal(result.ip.split(':').length, 8);
115
- });
116
-
117
- test('reverse DNS ends in .ip6.arpa', () => {
118
- const result = ip('::1');
119
- assert.ok(result.reverse.endsWith('.ip6.arpa'));
120
- });
121
- });
122
-
123
- describe('errors', () => {
124
- test('throws on empty string', () => {
125
- assert.throws(() => ip(''), /required/);
126
- });
127
-
128
- test('throws on invalid IPv4 octet > 255', () => {
129
- assert.throws(() => ip('256.0.0.1'), /Invalid IPv4/);
130
- });
131
-
132
- test('throws on incomplete IPv4', () => {
133
- assert.throws(() => ip('192.168.1'), /Invalid IPv4/);
134
- });
135
-
136
- test('throws on plain string', () => {
137
- assert.throws(() => ip('notanip'), /Invalid IP address format/);
138
- });
139
- });
140
- });
@@ -1,35 +0,0 @@
1
- import { strict as assert } from 'node:assert';
2
- import { describe, test } from 'node:test';
3
-
4
- import levenshtein from '../../src/modules/v4/levenshtein.js';
5
-
6
- describe('levenshtein', () => {
7
- test('identical strings: distance 0', () => {
8
- const result = levenshtein('hello', 'hello');
9
- assert.equal(result.distance, 0);
10
- });
11
-
12
- test('kitten / sitting: distance 3', () => {
13
- const result = levenshtein('kitten', 'sitting');
14
- assert.equal(result.distance, 3);
15
- });
16
-
17
- test('saturday / sunday: distance 3', () => {
18
- const result = levenshtein('saturday', 'sunday');
19
- assert.equal(result.distance, 3);
20
- });
21
-
22
- test('empty vs string: distance = string length', () => {
23
- const result = levenshtein('a', 'abc');
24
- assert.equal(result.distance, 2);
25
- });
26
-
27
- test('throws on missing first string', () => {
28
- assert.throws(() => levenshtein('', 'abc'), /first string/);
29
- });
30
-
31
- test('throws on string > 1000 chars', () => {
32
- const big = 'a'.repeat(1001);
33
- assert.throws(() => levenshtein(big, 'b'), /1000/);
34
- });
35
- });
@@ -1,54 +0,0 @@
1
- import { strict as assert } from 'node:assert';
2
- import { describe, test } from 'node:test';
3
-
4
- import palette from '../../src/modules/v4/palette.js';
5
-
6
- describe('palette', () => {
7
- test('complementary returns 2 colors', () => {
8
- const result = palette('#ff6600', 'complementary');
9
- assert.equal(result.colors.length, 2);
10
- assert.equal(result.type, 'complementary');
11
- });
12
-
13
- test('triadic returns 3 colors', () => {
14
- const result = palette('#ff6600', 'triadic');
15
- assert.equal(result.colors.length, 3);
16
- });
17
-
18
- test('analogous returns 5 colors', () => {
19
- const result = palette('#ff6600', 'analogous');
20
- assert.equal(result.colors.length, 5);
21
- });
22
-
23
- test('tetradic returns 4 colors', () => {
24
- const result = palette('#0066ff', 'tetradic');
25
- assert.equal(result.colors.length, 4);
26
- });
27
-
28
- test('split-complementary returns 3 colors', () => {
29
- const result = palette('#0066ff', 'split-complementary');
30
- assert.equal(result.colors.length, 3);
31
- });
32
-
33
- test('each color has hex/rgb/hsl', () => {
34
- const result = palette('#ff6600', 'triadic');
35
- for (const c of result.colors) {
36
- assert.match(c.hex, /^#[0-9a-f]{6}$/);
37
- assert.match(c.rgb, /^rgb\(/);
38
- assert.match(c.hsl, /^hsl\(/);
39
- }
40
- });
41
-
42
- test('base color is included', () => {
43
- const result = palette('#ff6600', 'complementary');
44
- assert.equal(result.base.hex, '#ff6600');
45
- });
46
-
47
- test('throws on invalid hex', () => {
48
- assert.throws(() => palette('#zzz', 'triadic'), /Invalid HEX/);
49
- });
50
-
51
- test('throws on unknown type', () => {
52
- assert.throws(() => palette('#ff6600', 'rainbow'), /must be one of/);
53
- });
54
- });