@haskou/value-objects 1.0.3 → 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.
@@ -0,0 +1,139 @@
1
+ import { createHash } from 'crypto';
2
+ import {
3
+ MD5Hash,
4
+ StringValueObject,
5
+ SHA256Hash,
6
+ SHA512Hash,
7
+ } from '../../../src';
8
+ import { InvalidHashError } from '../../../src/errors/InvalidHashError';
9
+
10
+ function compute(hashName: string, input: string | Buffer): string {
11
+ return createHash(hashName).update(input).digest('hex');
12
+ }
13
+
14
+ describe('MD5Hash', () => {
15
+ const sample = 'hello';
16
+ const expected = compute('md5', sample);
17
+
18
+ describe('isValid', () => {
19
+ it('returns true for a valid 32-character hex string', () => {
20
+ expect(MD5Hash.isValid(expected)).toBeTrue();
21
+ expect(MD5Hash.isValid(new StringValueObject(expected))).toBeTrue();
22
+ });
23
+
24
+ it('returns false for malformed values', () => {
25
+ expect(MD5Hash.isValid('not-a-hash')).toBeFalse();
26
+ expect(MD5Hash.isValid(expected.slice(0, 10))).toBeFalse();
27
+ });
28
+ });
29
+
30
+ describe('from()', () => {
31
+ it('computes the correct hash from a string', () => {
32
+ const hash = MD5Hash.from(sample);
33
+ expect(hash.valueOf()).toBe(expected);
34
+ });
35
+
36
+ it('computes the correct hash from a buffer', () => {
37
+ const buf = Buffer.from(sample);
38
+ const hash = MD5Hash.from(buf);
39
+ expect(hash.valueOf()).toBe(expected);
40
+ });
41
+
42
+ it('accepts a StringValueObject as input', () => {
43
+ const hash = MD5Hash.from(new StringValueObject(sample));
44
+ expect(hash.valueOf()).toBe(expected);
45
+ });
46
+ });
47
+
48
+ describe('constructor', () => {
49
+ it('creates instance for valid hash', () => {
50
+ expect(() => new MD5Hash(expected)).not.toThrow();
51
+ expect(new MD5Hash(expected)).toBeInstanceOf(MD5Hash);
52
+ });
53
+
54
+ it('throws InvalidHashError for invalid values', () => {
55
+ expect(() => new MD5Hash('123')).toThrow(InvalidHashError);
56
+ expect(() => new MD5Hash('g'.repeat(32))).toThrow(InvalidHashError);
57
+ });
58
+
59
+ it('returns a NullObject when constructed with nullish', () => {
60
+ expect(() => new MD5Hash(undefined as unknown as string)).not.toThrow();
61
+ });
62
+ });
63
+
64
+ describe('toBase64', () => {
65
+ it('produces the expected base64 string', () => {
66
+ const hash = new MD5Hash(expected);
67
+ const base64 = hash.toBase64().valueOf();
68
+ expect(base64).toBe(Buffer.from(expected, 'hex').toString('base64'));
69
+ });
70
+ });
71
+ });
72
+
73
+ // SHA256 and SHA512 share similar behavior
74
+ function hashTests(
75
+ Name: typeof SHA256Hash | typeof SHA512Hash,
76
+ algo: string,
77
+ length: number,
78
+ ) {
79
+ describe(Name.name, () => {
80
+ const sample = 'hello';
81
+ const expected = compute(algo, sample);
82
+
83
+ describe('isValid', () => {
84
+ it('returns true for valid string', () => {
85
+ expect(Name.isValid(expected)).toBeTrue();
86
+ expect(Name.isValid(new StringValueObject(expected))).toBeTrue();
87
+ });
88
+
89
+ it('returns false for invalid string', () => {
90
+ expect(Name.isValid('short')).toBeFalse();
91
+ expect(Name.isValid('g'.repeat(length))).toBeFalse();
92
+ });
93
+ });
94
+
95
+ describe('from()', () => {
96
+ it('computes the correct hash from a string', () => {
97
+ expect(Name.from(sample).valueOf()).toBe(expected);
98
+ });
99
+
100
+ it('computes the correct hash from a buffer', () => {
101
+ expect(Name.from(Buffer.from(sample)).valueOf()).toBe(expected);
102
+ });
103
+
104
+ it('accepts a StringValueObject as input', () => {
105
+ expect(Name.from(new StringValueObject(sample)).valueOf()).toBe(
106
+ expected,
107
+ );
108
+ });
109
+ });
110
+
111
+ describe('constructor', () => {
112
+ it('creates instance for valid hash', () => {
113
+ expect(() => new Name(expected)).not.toThrow();
114
+ expect(new Name(expected)).toBeInstanceOf(Name);
115
+ });
116
+
117
+ it('throws InvalidHashError for invalid values', () => {
118
+ expect(() => new Name('123')).toThrow(InvalidHashError);
119
+ expect(() => new Name('g'.repeat(length))).toThrow(InvalidHashError);
120
+ });
121
+
122
+ it('handles nullish input gracefully', () => {
123
+ expect(() => new Name(undefined as unknown as string)).not.toThrow();
124
+ });
125
+ });
126
+
127
+ describe('toBase64', () => {
128
+ it('produces the expected base64', () => {
129
+ const instance = new Name(expected);
130
+ expect(instance.toBase64().valueOf()).toBe(
131
+ Buffer.from(expected, 'hex').toString('base64'),
132
+ );
133
+ });
134
+ });
135
+ });
136
+ }
137
+
138
+ hashTests(SHA256Hash, 'sha256', 64);
139
+ hashTests(SHA512Hash, 'sha512', 128);
@@ -0,0 +1,187 @@
1
+ import { createHash } from 'crypto';
2
+
3
+ import {
4
+ InvalidHashError,
5
+ MD5Hash,
6
+ NullObject,
7
+ StringValueObject,
8
+ } from '../../../src';
9
+
10
+ function computeMD5(input: string | Buffer): string {
11
+ return createHash('md5').update(input).digest('hex');
12
+ }
13
+
14
+ describe('MD5Hash', () => {
15
+ describe('constructor', () => {
16
+ it('should return a NullValueObject when a Nullish is received', () => {
17
+ expect(() => new MD5Hash(undefined as unknown as string)).not.toThrow();
18
+ expect(
19
+ NullObject.isNullObject(new MD5Hash(undefined as unknown as string)),
20
+ ).toBeTrue();
21
+ });
22
+
23
+ const validMD5Hashes = [
24
+ '5d41402abc4b2a76b9719d911017c592', // MD5 of 'hello'
25
+ '098f6bcd4621d373cade4e832627b4f6', // MD5 of 'test'
26
+ '9e107d9d372bb6826bd81d3542a419d6', // MD5 of 'password'
27
+ 'd41d8cd98f00b204e9800998ecf8427e', // MD5 of empty string
28
+ 'a665a45920422f9d417e4867efdc4fb8', // MD5 of 'admin'
29
+ ];
30
+
31
+ it.each(validMD5Hashes)(
32
+ 'should create an MD5Hash instance for valid MD5 hash strings',
33
+ (hash) => {
34
+ expect(() => new MD5Hash(hash)).not.toThrow();
35
+ expect(new MD5Hash(hash).toString()).toBe(hash);
36
+ },
37
+ );
38
+
39
+ const invalidMD5Hashes = [
40
+ 'invalid-hash',
41
+ '1234567890123456789012345678901', // 31 chars
42
+ '123456789012345678901234567890123', // 33 chars
43
+ 'gggggggggggggggggggggggggggggggg', // 32 chars but invalid hex
44
+ '12345678901234567890123456789012g', // 32 chars with non-hex
45
+ '',
46
+ 'a'.repeat(31),
47
+ 'a'.repeat(33),
48
+ ];
49
+
50
+ it.each(invalidMD5Hashes)(
51
+ 'should throw InvalidHashError for invalid MD5 hash strings',
52
+ (hash) => {
53
+ expect(() => new MD5Hash(hash)).toThrow(InvalidHashError);
54
+ },
55
+ );
56
+
57
+ it('should accept another StringValueObject', () => {
58
+ const hashString = new StringValueObject(
59
+ '5d41402abc4b2a76b9719d911017c592',
60
+ );
61
+ const hash = new MD5Hash(hashString);
62
+ expect(hash.toString()).toBe('5d41402abc4b2a76b9719d911017c592');
63
+ });
64
+
65
+ it('should validate hash format when constructed from StringValueObject', () => {
66
+ const invalidHashString = new StringValueObject('invalid-hash');
67
+ expect(() => new MD5Hash(invalidHashString)).toThrow(InvalidHashError);
68
+ });
69
+ });
70
+
71
+ describe('static methods', () => {
72
+ describe('isValid', () => {
73
+ it('should return true for valid MD5 hashes', () => {
74
+ const validHash = '5d41402abc4b2a76b9719d911017c592';
75
+ expect(MD5Hash.isValid(validHash)).toBeTrue();
76
+ expect(MD5Hash.isValid(new StringValueObject(validHash))).toBeTrue();
77
+ });
78
+
79
+ it('should return false for invalid MD5 hashes', () => {
80
+ expect(MD5Hash.isValid('invalid')).toBeFalse();
81
+ expect(MD5Hash.isValid('123')).toBeFalse();
82
+ expect(MD5Hash.isValid('gggggggggggggggggggggggggggggggg')).toBeFalse();
83
+ });
84
+ });
85
+
86
+ describe('from', () => {
87
+ it('should compute MD5 hash from string', () => {
88
+ const input = 'hello';
89
+ const expected = computeMD5(input);
90
+ const hash = MD5Hash.from(input);
91
+ expect(hash.valueOf()).toBe(expected);
92
+ });
93
+
94
+ it('should compute MD5 hash from Buffer', () => {
95
+ const input = Buffer.from('hello');
96
+ const expected = computeMD5(input);
97
+ const hash = MD5Hash.from(input);
98
+ expect(hash.valueOf()).toBe(expected);
99
+ });
100
+
101
+ it('should compute MD5 hash from StringValueObject', () => {
102
+ const input = new StringValueObject('hello');
103
+ const expected = computeMD5(input.valueOf());
104
+ const hash = MD5Hash.from(input);
105
+ expect(hash.valueOf()).toBe(expected);
106
+ });
107
+ });
108
+ });
109
+
110
+ describe('inheritance and ValueObject behavior', () => {
111
+ it('should inherit from ValueObject', () => {
112
+ const hash = new MD5Hash('5d41402abc4b2a76b9719d911017c592');
113
+ expect(hash).toBeInstanceOf(MD5Hash);
114
+ expect(hash.valueOf).toBeDefined();
115
+ expect(hash.isEqual).toBeDefined();
116
+ expect(hash.toString).toBeDefined();
117
+ });
118
+
119
+ it('should implement valueOf() method correctly', () => {
120
+ const hashValue = '098f6bcd4621d373cade4e832627b4f6';
121
+ const hash = new MD5Hash(hashValue);
122
+ expect(hash.valueOf()).toBe(hashValue);
123
+ });
124
+
125
+ it('should implement toString() method correctly', () => {
126
+ const hashValue = '9e107d9d372bb6826bd81d3542a419d6';
127
+ const hash = new MD5Hash(hashValue);
128
+ expect(hash.toString()).toBe(hashValue);
129
+ });
130
+
131
+ it('should implement isEqual() method correctly', () => {
132
+ const hashValue = '5d41402abc4b2a76b9719d911017c592';
133
+ const hash1 = new MD5Hash(hashValue);
134
+ const hash2 = new MD5Hash(hashValue);
135
+ const hash3 = new MD5Hash('098f6bcd4621d373cade4e832627b4f6');
136
+
137
+ expect(hash1.isEqual(hash2)).toBeTrue();
138
+ expect(hash1.isEqual(hash3)).toBeFalse();
139
+ expect(hash1.isEqual(hashValue)).toBeTrue();
140
+ expect(hash1.isEqual('098f6bcd4621d373cade4e832627b4f6')).toBeFalse();
141
+ });
142
+
143
+ it('should compare with string values using isEqual', () => {
144
+ const hash = new MD5Hash('5d41402abc4b2a76b9719d911017c592');
145
+ expect(hash.isEqual('5d41402abc4b2a76b9719d911017c592')).toBeTrue();
146
+ expect(hash.isEqual('098f6bcd4621d373cade4e832627b4f6')).toBeFalse();
147
+ });
148
+
149
+ it('should implement clone() method correctly inherited from ValueObject', () => {
150
+ const originalValue = '5d41402abc4b2a76b9719d911017c592';
151
+ const original = new MD5Hash(originalValue);
152
+ const cloned = (original as any).clone();
153
+
154
+ expect(cloned).toBeInstanceOf(MD5Hash);
155
+ expect(cloned.valueOf()).toBe(originalValue);
156
+ expect(cloned.toString()).toBe(originalValue);
157
+ expect(cloned.isEqual(original)).toBeTrue();
158
+ expect(cloned).not.toBe(original); // Different instances
159
+ });
160
+
161
+ it('should maintain hash validation in cloned instances', () => {
162
+ const original = new MD5Hash('5d41402abc4b2a76b9719d911017c592');
163
+ const cloned = (original as any).clone();
164
+
165
+ expect(cloned).toBeInstanceOf(MD5Hash);
166
+ expect(cloned.valueOf()).toBe('5d41402abc4b2a76b9719d911017c592');
167
+
168
+ // Cloned instance should still be a valid hash
169
+ expect(() => cloned.toString()).not.toThrow();
170
+ expect(cloned.toString()).toBe('5d41402abc4b2a76b9719d911017c592');
171
+ });
172
+ });
173
+
174
+ describe('instance methods', () => {
175
+ describe('toBase64', () => {
176
+ it('should convert hash to base64 correctly', () => {
177
+ const hashValue = '5d41402abc4b2a76b9719d911017c592';
178
+ const hash = new MD5Hash(hashValue);
179
+ const base64 = hash.toBase64();
180
+ expect(base64).toBeInstanceOf(StringValueObject);
181
+ expect(base64.valueOf()).toBe(
182
+ Buffer.from(hashValue, 'hex').toString('base64'),
183
+ );
184
+ });
185
+ });
186
+ });
187
+ });
@@ -0,0 +1,220 @@
1
+ import { createHash } from 'crypto';
2
+ import {
3
+ SHA256Hash,
4
+ NullObject,
5
+ InvalidHashError,
6
+ StringValueObject,
7
+ } from '../../../src';
8
+
9
+ function computeSHA256(input: string | Buffer): string {
10
+ return createHash('sha256').update(input).digest('hex');
11
+ }
12
+
13
+ describe('SHA256Hash', () => {
14
+ describe('constructor', () => {
15
+ it('should return a NullValueObject when a Nullish is received', () => {
16
+ expect(
17
+ () => new SHA256Hash(undefined as unknown as string),
18
+ ).not.toThrow();
19
+ expect(
20
+ NullObject.isNullObject(new SHA256Hash(undefined as unknown as string)),
21
+ ).toBeTrue();
22
+ });
23
+
24
+ const validSHA256Hashes = [
25
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', // SHA256 of 'hello'
26
+ '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', // SHA256 of 'test'
27
+ '5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8', // SHA256 of 'password'
28
+ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', // SHA256 of empty string
29
+ '8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918', // SHA256 of 'admin'
30
+ ];
31
+
32
+ it.each(validSHA256Hashes)(
33
+ 'should create an SHA256Hash instance for valid SHA256 hash strings',
34
+ (hash) => {
35
+ expect(() => new SHA256Hash(hash)).not.toThrow();
36
+ expect(new SHA256Hash(hash).toString()).toBe(hash);
37
+ },
38
+ );
39
+
40
+ const invalidSHA256Hashes = [
41
+ 'invalid-hash',
42
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b982', // 63 chars
43
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b98245', // 65 chars
44
+ 'gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg', // 64 chars but invalid hex
45
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b982g', // 64 chars with non-hex
46
+ '',
47
+ 'a'.repeat(63),
48
+ 'a'.repeat(65),
49
+ ];
50
+
51
+ it.each(invalidSHA256Hashes)(
52
+ 'should throw InvalidHashError for invalid SHA256 hash strings',
53
+ (hash) => {
54
+ expect(() => new SHA256Hash(hash)).toThrow(InvalidHashError);
55
+ },
56
+ );
57
+
58
+ it('should accept another StringValueObject', () => {
59
+ const hashString = new StringValueObject(
60
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
61
+ );
62
+ const hash = new SHA256Hash(hashString);
63
+ expect(hash.toString()).toBe(
64
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
65
+ );
66
+ });
67
+
68
+ it('should validate hash format when constructed from StringValueObject', () => {
69
+ const invalidHashString = new StringValueObject('invalid-hash');
70
+ expect(() => new SHA256Hash(invalidHashString)).toThrow(InvalidHashError);
71
+ });
72
+ });
73
+
74
+ describe('static methods', () => {
75
+ describe('isValid', () => {
76
+ it('should return true for valid SHA256 hashes', () => {
77
+ const validHash =
78
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824';
79
+ expect(SHA256Hash.isValid(validHash)).toBeTrue();
80
+ expect(SHA256Hash.isValid(new StringValueObject(validHash))).toBeTrue();
81
+ });
82
+
83
+ it('should return false for invalid SHA256 hashes', () => {
84
+ expect(SHA256Hash.isValid('invalid')).toBeFalse();
85
+ expect(SHA256Hash.isValid('123')).toBeFalse();
86
+ expect(SHA256Hash.isValid('g'.repeat(64))).toBeFalse();
87
+ });
88
+ });
89
+
90
+ describe('from', () => {
91
+ it('should compute SHA256 hash from string', () => {
92
+ const input = 'hello';
93
+ const expected = computeSHA256(input);
94
+ const hash = SHA256Hash.from(input);
95
+ expect(hash.valueOf()).toBe(expected);
96
+ });
97
+
98
+ it('should compute SHA256 hash from Buffer', () => {
99
+ const input = Buffer.from('hello');
100
+ const expected = computeSHA256(input);
101
+ const hash = SHA256Hash.from(input);
102
+ expect(hash.valueOf()).toBe(expected);
103
+ });
104
+
105
+ it('should compute SHA256 hash from StringValueObject', () => {
106
+ const input = new StringValueObject('hello');
107
+ const expected = computeSHA256(input.valueOf());
108
+ const hash = SHA256Hash.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 SHA256Hash(
117
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
118
+ );
119
+ expect(hash).toBeInstanceOf(SHA256Hash);
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
+ '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08';
128
+ const hash = new SHA256Hash(hashValue);
129
+ expect(hash.valueOf()).toBe(hashValue);
130
+ });
131
+
132
+ it('should implement toString() method correctly', () => {
133
+ const hashValue =
134
+ '5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8';
135
+ const hash = new SHA256Hash(hashValue);
136
+ expect(hash.toString()).toBe(hashValue);
137
+ });
138
+
139
+ it('should implement isEqual() method correctly', () => {
140
+ const hashValue =
141
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824';
142
+ const hash1 = new SHA256Hash(hashValue);
143
+ const hash2 = new SHA256Hash(hashValue);
144
+ const hash3 = new SHA256Hash(
145
+ '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08',
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
+ '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08',
154
+ ),
155
+ ).toBeFalse();
156
+ });
157
+
158
+ it('should compare with string values using isEqual', () => {
159
+ const hash = new SHA256Hash(
160
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
161
+ );
162
+ expect(
163
+ hash.isEqual(
164
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
165
+ ),
166
+ ).toBeTrue();
167
+ expect(
168
+ hash.isEqual(
169
+ '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08',
170
+ ),
171
+ ).toBeFalse();
172
+ });
173
+
174
+ it('should implement clone() method correctly inherited from ValueObject', () => {
175
+ const originalValue =
176
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824';
177
+ const original = new SHA256Hash(originalValue);
178
+ const cloned = (original as any).clone();
179
+
180
+ expect(cloned).toBeInstanceOf(SHA256Hash);
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 SHA256Hash(
189
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
190
+ );
191
+ const cloned = (original as any).clone();
192
+
193
+ expect(cloned).toBeInstanceOf(SHA256Hash);
194
+ expect(cloned.valueOf()).toBe(
195
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
196
+ );
197
+
198
+ // Cloned instance should still be a valid hash
199
+ expect(() => cloned.toString()).not.toThrow();
200
+ expect(cloned.toString()).toBe(
201
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
202
+ );
203
+ });
204
+ });
205
+
206
+ describe('instance methods', () => {
207
+ describe('toBase64', () => {
208
+ it('should convert hash to base64 correctly', () => {
209
+ const hashValue =
210
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824';
211
+ const hash = new SHA256Hash(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
+ });