@haskou/value-objects 1.0.2 → 1.1.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.
- package/LICENSE.txt +21 -0
- package/README.md +22 -3
- package/TECHNICAL_DOCUMENTATION.md +166 -0
- package/package.json +6 -1
- package/src/errors/InvalidFormatError.ts +7 -0
- package/src/errors/InvalidHashError.ts +7 -0
- package/src/errors/InvalidLengthError.ts +9 -0
- package/src/errors/InvalidValueError.ts +7 -0
- package/src/errors/index.ts +3 -0
- package/src/patterns/index.ts +0 -2
- package/src/value-objects/Color.ts +1 -1
- package/src/value-objects/Email.ts +1 -1
- package/src/value-objects/Enum.ts +3 -3
- package/src/value-objects/Integer.ts +1 -1
- package/src/value-objects/NumberValueObject.ts +2 -2
- package/src/value-objects/PositiveNumber.ts +1 -1
- package/src/value-objects/StringValueObject.ts +2 -2
- package/src/{patterns → value-objects}/ValueObject.ts +1 -2
- package/src/value-objects/hashes/Hash.ts +19 -0
- package/src/value-objects/hashes/MD5Hash.ts +32 -0
- package/src/value-objects/hashes/SHA256Hash.ts +38 -0
- package/src/value-objects/hashes/SHA512Hash.ts +38 -0
- package/src/value-objects/hashes/index.ts +4 -0
- package/src/value-objects/ids/ShortId.ts +34 -0
- package/src/value-objects/ids/UUID.ts +34 -0
- package/src/value-objects/index.ts +2 -0
- package/src/value-objects/time/CalendarDay.ts +1 -1
- package/src/value-objects/time/MonthOfYear.ts +1 -1
- package/src/value-objects/time/Timestamp.ts +1 -1
- package/src/value-objects/time/TimestampInterval.ts +1 -1
- package/src/value-objects/time/Year.ts +1 -1
- package/tests/value-objects/Email.spec.ts +2 -6
- package/tests/value-objects/hashes/Hashes.spec.ts +139 -0
- package/tests/value-objects/hashes/MD5Hash.spec.ts +187 -0
- package/tests/value-objects/hashes/SHA256Hash.spec.ts +220 -0
- package/tests/value-objects/hashes/SHA512Hash.spec.ts +220 -0
- package/tests/value-objects/ids/ShortId.spec.ts +34 -0
- package/tests/value-objects/ids/UUID.spec.ts +37 -0
- /package/src/{patterns → value-objects}/NullObject.ts +0 -0
- /package/tests/{patterns → value-objects}/NullObject.spec.ts +0 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import {
|
|
3
|
+
SHA512Hash,
|
|
4
|
+
NullObject,
|
|
5
|
+
InvalidHashError,
|
|
6
|
+
StringValueObject,
|
|
7
|
+
} from '../../../src';
|
|
8
|
+
|
|
9
|
+
function computeSHA512(input: string | Buffer): string {
|
|
10
|
+
return createHash('sha512').update(input).digest('hex');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe('SHA512Hash', () => {
|
|
14
|
+
describe('constructor', () => {
|
|
15
|
+
it('should return a NullValueObject when a Nullish is received', () => {
|
|
16
|
+
expect(
|
|
17
|
+
() => new SHA512Hash(undefined as unknown as string),
|
|
18
|
+
).not.toThrow();
|
|
19
|
+
expect(
|
|
20
|
+
NullObject.isNullObject(new SHA512Hash(undefined as unknown as string)),
|
|
21
|
+
).toBeTrue();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const validSHA512Hashes = [
|
|
25
|
+
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043', // SHA512 of 'hello'
|
|
26
|
+
'ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff', // SHA512 of 'test'
|
|
27
|
+
'b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86', // SHA512 of 'password'
|
|
28
|
+
'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e', // SHA512 of empty string
|
|
29
|
+
'c7ad44cbad762a5da0a452f9e854fdc1e0e7a52a38015f23f3eab1d80b931dd472634dfac71cd34ebc35d16ab7fb8a90c81f975113d6c7538dc69dd8de9077ec', // SHA512 of 'admin'
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
it.each(validSHA512Hashes)(
|
|
33
|
+
'should create an SHA512Hash instance for valid SHA512 hash strings',
|
|
34
|
+
(hash) => {
|
|
35
|
+
expect(() => new SHA512Hash(hash)).not.toThrow();
|
|
36
|
+
expect(new SHA512Hash(hash).toString()).toBe(hash);
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const invalidSHA512Hashes = [
|
|
41
|
+
'invalid-hash',
|
|
42
|
+
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca7232', // 127 chars
|
|
43
|
+
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca723234', // 129 chars
|
|
44
|
+
'g'.repeat(128), // 128 chars but invalid hex
|
|
45
|
+
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca7232g', // 128 chars with non-hex
|
|
46
|
+
'',
|
|
47
|
+
'a'.repeat(127),
|
|
48
|
+
'a'.repeat(129),
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
it.each(invalidSHA512Hashes)(
|
|
52
|
+
'should throw InvalidHashError for invalid SHA512 hash strings',
|
|
53
|
+
(hash) => {
|
|
54
|
+
expect(() => new SHA512Hash(hash)).toThrow(InvalidHashError);
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
it('should accept another StringValueObject', () => {
|
|
59
|
+
const hashString = new StringValueObject(
|
|
60
|
+
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043',
|
|
61
|
+
);
|
|
62
|
+
const hash = new SHA512Hash(hashString);
|
|
63
|
+
expect(hash.toString()).toBe(
|
|
64
|
+
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043',
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should validate hash format when constructed from StringValueObject', () => {
|
|
69
|
+
const invalidHashString = new StringValueObject('invalid-hash');
|
|
70
|
+
expect(() => new SHA512Hash(invalidHashString)).toThrow(InvalidHashError);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('static methods', () => {
|
|
75
|
+
describe('isValid', () => {
|
|
76
|
+
it('should return true for valid SHA512 hashes', () => {
|
|
77
|
+
const validHash =
|
|
78
|
+
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043';
|
|
79
|
+
expect(SHA512Hash.isValid(validHash)).toBeTrue();
|
|
80
|
+
expect(SHA512Hash.isValid(new StringValueObject(validHash))).toBeTrue();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should return false for invalid SHA512 hashes', () => {
|
|
84
|
+
expect(SHA512Hash.isValid('invalid')).toBeFalse();
|
|
85
|
+
expect(SHA512Hash.isValid('123')).toBeFalse();
|
|
86
|
+
expect(SHA512Hash.isValid('g'.repeat(128))).toBeFalse();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('from', () => {
|
|
91
|
+
it('should compute SHA512 hash from string', () => {
|
|
92
|
+
const input = 'hello';
|
|
93
|
+
const expected = computeSHA512(input);
|
|
94
|
+
const hash = SHA512Hash.from(input);
|
|
95
|
+
expect(hash.valueOf()).toBe(expected);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should compute SHA512 hash from Buffer', () => {
|
|
99
|
+
const input = Buffer.from('hello');
|
|
100
|
+
const expected = computeSHA512(input);
|
|
101
|
+
const hash = SHA512Hash.from(input);
|
|
102
|
+
expect(hash.valueOf()).toBe(expected);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should compute SHA512 hash from StringValueObject', () => {
|
|
106
|
+
const input = new StringValueObject('hello');
|
|
107
|
+
const expected = computeSHA512(input.valueOf());
|
|
108
|
+
const hash = SHA512Hash.from(input);
|
|
109
|
+
expect(hash.valueOf()).toBe(expected);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('inheritance and ValueObject behavior', () => {
|
|
115
|
+
it('should inherit from ValueObject', () => {
|
|
116
|
+
const hash = new SHA512Hash(
|
|
117
|
+
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043',
|
|
118
|
+
);
|
|
119
|
+
expect(hash).toBeInstanceOf(SHA512Hash);
|
|
120
|
+
expect(hash.valueOf).toBeDefined();
|
|
121
|
+
expect(hash.isEqual).toBeDefined();
|
|
122
|
+
expect(hash.toString).toBeDefined();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should implement valueOf() method correctly', () => {
|
|
126
|
+
const hashValue =
|
|
127
|
+
'ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff';
|
|
128
|
+
const hash = new SHA512Hash(hashValue);
|
|
129
|
+
expect(hash.valueOf()).toBe(hashValue);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should implement toString() method correctly', () => {
|
|
133
|
+
const hashValue =
|
|
134
|
+
'b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86';
|
|
135
|
+
const hash = new SHA512Hash(hashValue);
|
|
136
|
+
expect(hash.toString()).toBe(hashValue);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should implement isEqual() method correctly', () => {
|
|
140
|
+
const hashValue =
|
|
141
|
+
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043';
|
|
142
|
+
const hash1 = new SHA512Hash(hashValue);
|
|
143
|
+
const hash2 = new SHA512Hash(hashValue);
|
|
144
|
+
const hash3 = new SHA512Hash(
|
|
145
|
+
'ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff',
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
expect(hash1.isEqual(hash2)).toBeTrue();
|
|
149
|
+
expect(hash1.isEqual(hash3)).toBeFalse();
|
|
150
|
+
expect(hash1.isEqual(hashValue)).toBeTrue();
|
|
151
|
+
expect(
|
|
152
|
+
hash1.isEqual(
|
|
153
|
+
'ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff',
|
|
154
|
+
),
|
|
155
|
+
).toBeFalse();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should compare with string values using isEqual', () => {
|
|
159
|
+
const hash = new SHA512Hash(
|
|
160
|
+
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043',
|
|
161
|
+
);
|
|
162
|
+
expect(
|
|
163
|
+
hash.isEqual(
|
|
164
|
+
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043',
|
|
165
|
+
),
|
|
166
|
+
).toBeTrue();
|
|
167
|
+
expect(
|
|
168
|
+
hash.isEqual(
|
|
169
|
+
'ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff',
|
|
170
|
+
),
|
|
171
|
+
).toBeFalse();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should implement clone() method correctly inherited from ValueObject', () => {
|
|
175
|
+
const originalValue =
|
|
176
|
+
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043';
|
|
177
|
+
const original = new SHA512Hash(originalValue);
|
|
178
|
+
const cloned = (original as any).clone();
|
|
179
|
+
|
|
180
|
+
expect(cloned).toBeInstanceOf(SHA512Hash);
|
|
181
|
+
expect(cloned.valueOf()).toBe(originalValue);
|
|
182
|
+
expect(cloned.toString()).toBe(originalValue);
|
|
183
|
+
expect(cloned.isEqual(original)).toBeTrue();
|
|
184
|
+
expect(cloned).not.toBe(original); // Different instances
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should maintain hash validation in cloned instances', () => {
|
|
188
|
+
const original = new SHA512Hash(
|
|
189
|
+
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043',
|
|
190
|
+
);
|
|
191
|
+
const cloned = (original as any).clone();
|
|
192
|
+
|
|
193
|
+
expect(cloned).toBeInstanceOf(SHA512Hash);
|
|
194
|
+
expect(cloned.valueOf()).toBe(
|
|
195
|
+
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043',
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// Cloned instance should still be a valid hash
|
|
199
|
+
expect(() => cloned.toString()).not.toThrow();
|
|
200
|
+
expect(cloned.toString()).toBe(
|
|
201
|
+
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043',
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe('instance methods', () => {
|
|
207
|
+
describe('toBase64', () => {
|
|
208
|
+
it('should convert hash to base64 correctly', () => {
|
|
209
|
+
const hashValue =
|
|
210
|
+
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043';
|
|
211
|
+
const hash = new SHA512Hash(hashValue);
|
|
212
|
+
const base64 = hash.toBase64();
|
|
213
|
+
expect(base64).toBeInstanceOf(StringValueObject);
|
|
214
|
+
expect(base64.valueOf()).toBe(
|
|
215
|
+
Buffer.from(hashValue, 'hex').toString('base64'),
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { InvalidFormatError } from '../../../src/errors/InvalidFormatError';
|
|
2
|
+
import { InvalidLengthError } from '../../../src/errors/InvalidLengthError';
|
|
3
|
+
import { InvalidValueError } from '../../../src/errors/InvalidValueError';
|
|
4
|
+
import { ShortId } from '../../../src/value-objects/ids/ShortId';
|
|
5
|
+
|
|
6
|
+
describe('ShortId', () => {
|
|
7
|
+
describe('generate', () => {
|
|
8
|
+
it('should generate a valid ShortId', () => {
|
|
9
|
+
const id = ShortId.generate();
|
|
10
|
+
expect(id).toBeInstanceOf(ShortId);
|
|
11
|
+
expect(id.toString()).toHaveLength(24);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
describe('constructor', () => {
|
|
15
|
+
it('should throw invalid length error when constructor receives a empty string', () => {
|
|
16
|
+
expect(() => new ShortId('')).toThrow(InvalidLengthError);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should throw invalid format when value is not a valid id', () => {
|
|
20
|
+
expect(() => new ShortId('123')).toThrow(InvalidLengthError);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should be a valid id when it's 24 length string", () => {
|
|
24
|
+
expect(() => new ShortId('12345678901234567890abcd')).not.toThrow();
|
|
25
|
+
expect(new ShortId('12345678901234567890abcd')).toBeInstanceOf(ShortId);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should be invalid id when it's 24 length but invalid characters", () => {
|
|
29
|
+
expect(() => new ShortId('123456789@123456789#abcd')).toThrow(
|
|
30
|
+
InvalidFormatError,
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { InvalidFormatError } from '../../../src/errors/InvalidFormatError';
|
|
2
|
+
import { InvalidLengthError } from '../../../src/errors/InvalidLengthError';
|
|
3
|
+
import { UUID } from '../../../src/value-objects/ids/UUID';
|
|
4
|
+
|
|
5
|
+
describe('UUID', () => {
|
|
6
|
+
describe('generate', () => {
|
|
7
|
+
it('should generate a valid LongId', () => {
|
|
8
|
+
const id = UUID.generate();
|
|
9
|
+
expect(id).toBeInstanceOf(UUID);
|
|
10
|
+
expect(id.toString()).toHaveLength(36);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
describe('constructor', () => {
|
|
14
|
+
it('should throw invalid length error when constructor receives a empty string', () => {
|
|
15
|
+
expect(() => new UUID('')).toThrow(InvalidLengthError);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should throw invalid format when value is not a valid id', () => {
|
|
19
|
+
expect(() => new UUID('123')).toThrow(InvalidLengthError);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should be a valid id when it's 36 length string", () => {
|
|
23
|
+
expect(
|
|
24
|
+
() => new UUID('8ab0b0f7-7324-4637-bc6b-109326f081c0'),
|
|
25
|
+
).not.toThrow();
|
|
26
|
+
expect(new UUID('8ab0b0f7-7324-4637-bc6b-109326f081c0')).toBeInstanceOf(
|
|
27
|
+
UUID,
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should be invalid id when it's 36 length but invalid characters", () => {
|
|
32
|
+
expect(() => new UUID('8ab0b0f7-7324#5637-bc6b-109326f081c0')).toThrow(
|
|
33
|
+
InvalidFormatError,
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
File without changes
|
|
File without changes
|