hash_validator 1.1.1 → 2.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +1 -1
  3. data/.ruby-version +1 -1
  4. data/README.md +315 -56
  5. data/hash_validator.gemspec +1 -1
  6. data/lib/hash_validator/configuration.rb +16 -0
  7. data/lib/hash_validator/validators/alpha_validator.rb +15 -0
  8. data/lib/hash_validator/validators/alphanumeric_validator.rb +15 -0
  9. data/lib/hash_validator/validators/array_validator.rb +1 -1
  10. data/lib/hash_validator/validators/base.rb +28 -3
  11. data/lib/hash_validator/validators/boolean_validator.rb +3 -5
  12. data/lib/hash_validator/validators/class_validator.rb +1 -1
  13. data/lib/hash_validator/validators/digits_validator.rb +15 -0
  14. data/lib/hash_validator/validators/dynamic_func_validator.rb +26 -0
  15. data/lib/hash_validator/validators/dynamic_pattern_validator.rb +23 -0
  16. data/lib/hash_validator/validators/email_validator.rb +4 -6
  17. data/lib/hash_validator/validators/enumerable_validator.rb +4 -6
  18. data/lib/hash_validator/validators/hash_validator.rb +8 -3
  19. data/lib/hash_validator/validators/hex_color_validator.rb +15 -0
  20. data/lib/hash_validator/validators/ip_validator.rb +22 -0
  21. data/lib/hash_validator/validators/ipv4_validator.rb +18 -0
  22. data/lib/hash_validator/validators/ipv6_validator.rb +22 -0
  23. data/lib/hash_validator/validators/json_validator.rb +21 -0
  24. data/lib/hash_validator/validators/lambda_validator.rb +5 -8
  25. data/lib/hash_validator/validators/many_validator.rb +3 -3
  26. data/lib/hash_validator/validators/multiple_validator.rb +1 -1
  27. data/lib/hash_validator/validators/optional_validator.rb +1 -1
  28. data/lib/hash_validator/validators/presence_validator.rb +4 -6
  29. data/lib/hash_validator/validators/regex_validator.rb +4 -6
  30. data/lib/hash_validator/validators/simple_type_validators.rb +1 -1
  31. data/lib/hash_validator/validators/simple_validator.rb +2 -4
  32. data/lib/hash_validator/validators/url_validator.rb +21 -0
  33. data/lib/hash_validator/validators.rb +46 -4
  34. data/lib/hash_validator/version.rb +1 -1
  35. data/lib/hash_validator.rb +1 -0
  36. data/spec/configuration_spec.rb +189 -0
  37. data/spec/hash_validator_spec.rb +4 -4
  38. data/spec/validators/alpha_validator_spec.rb +93 -0
  39. data/spec/validators/alphanumeric_validator_spec.rb +99 -0
  40. data/spec/validators/base_spec.rb +2 -2
  41. data/spec/validators/digits_validator_spec.rb +99 -0
  42. data/spec/validators/dynamic_func_validator_spec.rb +252 -0
  43. data/spec/validators/dynamic_pattern_validator_spec.rb +150 -0
  44. data/spec/validators/hash_validator_spec.rb +102 -0
  45. data/spec/validators/hex_color_validator_spec.rb +111 -0
  46. data/spec/validators/ip_validator_spec.rb +105 -0
  47. data/spec/validators/ipv4_validator_spec.rb +99 -0
  48. data/spec/validators/ipv6_validator_spec.rb +99 -0
  49. data/spec/validators/json_validator_spec.rb +88 -0
  50. data/spec/validators/url_validator_spec.rb +75 -0
  51. data/spec/validators/user_defined_spec.rb +2 -2
  52. metadata +42 -4
@@ -0,0 +1,150 @@
1
+ require 'spec_helper'
2
+
3
+ describe HashValidator::Validator::DynamicPatternValidator do
4
+ describe 'Pattern-based validator' do
5
+ let(:odd_pattern_validator) do
6
+ HashValidator::Validator::DynamicPatternValidator.new(
7
+ 'odd_pattern',
8
+ /\A\d*[13579]\z/,
9
+ 'is not an odd number'
10
+ )
11
+ end
12
+
13
+ let(:postal_code_validator) do
14
+ HashValidator::Validator::DynamicPatternValidator.new(
15
+ 'us_postal',
16
+ /\A\d{5}(-\d{4})?\z/,
17
+ 'is not a valid US postal code'
18
+ )
19
+ end
20
+
21
+ let(:errors) { Hash.new }
22
+
23
+ describe '#initialize' do
24
+ it 'requires a valid regular expression' do
25
+ expect {
26
+ HashValidator::Validator::DynamicPatternValidator.new('test', 'not_a_regex')
27
+ }.to raise_error(ArgumentError, 'Pattern must be a regular expression')
28
+ end
29
+
30
+ it 'accepts a custom error message' do
31
+ validator = HashValidator::Validator::DynamicPatternValidator.new(
32
+ 'test',
33
+ /test/,
34
+ 'custom error'
35
+ )
36
+ expect(validator.error_message).to eq('custom error')
37
+ end
38
+
39
+ it 'uses default error message when none provided' do
40
+ validator = HashValidator::Validator::DynamicPatternValidator.new('test', /test/)
41
+ expect(validator.error_message).to eq('test required')
42
+ end
43
+ end
44
+
45
+ describe '#should_validate?' do
46
+ it 'validates the correct name' do
47
+ expect(odd_pattern_validator.should_validate?('odd_pattern')).to eq true
48
+ end
49
+
50
+ it 'does not validate other names' do
51
+ expect(odd_pattern_validator.should_validate?('even_pattern')).to eq false
52
+ expect(odd_pattern_validator.should_validate?('string')).to eq false
53
+ end
54
+ end
55
+
56
+ describe '#validate' do
57
+ context 'with odd number pattern' do
58
+ it 'validates odd numbers correctly' do
59
+ odd_pattern_validator.validate(:key, '1', {}, errors)
60
+ expect(errors).to be_empty
61
+
62
+ errors.clear
63
+ odd_pattern_validator.validate(:key, '13', {}, errors)
64
+ expect(errors).to be_empty
65
+
66
+ errors.clear
67
+ odd_pattern_validator.validate(:key, '999', {}, errors)
68
+ expect(errors).to be_empty
69
+ end
70
+
71
+ it 'rejects even numbers' do
72
+ odd_pattern_validator.validate(:key, '2', {}, errors)
73
+ expect(errors).to eq({ key: 'is not an odd number' })
74
+
75
+ errors.clear
76
+ odd_pattern_validator.validate(:key, '100', {}, errors)
77
+ expect(errors).to eq({ key: 'is not an odd number' })
78
+ end
79
+
80
+ it 'rejects non-numeric strings' do
81
+ odd_pattern_validator.validate(:key, 'abc', {}, errors)
82
+ expect(errors).to eq({ key: 'is not an odd number' })
83
+ end
84
+
85
+ it 'converts non-string values to strings' do
86
+ odd_pattern_validator.validate(:key, 13, {}, errors)
87
+ expect(errors).to be_empty
88
+
89
+ errors.clear
90
+ odd_pattern_validator.validate(:key, 14, {}, errors)
91
+ expect(errors).to eq({ key: 'is not an odd number' })
92
+ end
93
+ end
94
+
95
+ context 'with postal code pattern' do
96
+ it 'validates correct postal codes' do
97
+ postal_code_validator.validate(:key, '12345', {}, errors)
98
+ expect(errors).to be_empty
99
+
100
+ errors.clear
101
+ postal_code_validator.validate(:key, '12345-6789', {}, errors)
102
+ expect(errors).to be_empty
103
+ end
104
+
105
+ it 'rejects invalid postal codes' do
106
+ postal_code_validator.validate(:key, '1234', {}, errors)
107
+ expect(errors).to eq({ key: 'is not a valid US postal code' })
108
+
109
+ errors.clear
110
+ postal_code_validator.validate(:key, '12345-678', {}, errors)
111
+ expect(errors).to eq({ key: 'is not a valid US postal code' })
112
+
113
+ errors.clear
114
+ postal_code_validator.validate(:key, 'ABCDE', {}, errors)
115
+ expect(errors).to eq({ key: 'is not a valid US postal code' })
116
+ end
117
+ end
118
+ end
119
+
120
+ describe 'Integration with HashValidator.add_validator' do
121
+ before do
122
+ HashValidator.add_validator('phone_number',
123
+ pattern: /\A\d{3}-\d{3}-\d{4}\z/,
124
+ error_message: 'is not a valid phone number')
125
+ end
126
+
127
+ after do
128
+ HashValidator.remove_validator('phone_number')
129
+ end
130
+
131
+ it 'can be registered and used via add_validator' do
132
+ validator = HashValidator.validate(
133
+ { phone: '555-123-4567' },
134
+ { phone: 'phone_number' }
135
+ )
136
+ expect(validator.valid?).to eq true
137
+ expect(validator.errors).to be_empty
138
+ end
139
+
140
+ it 'returns errors for invalid values' do
141
+ validator = HashValidator.validate(
142
+ { phone: '5551234567' },
143
+ { phone: 'phone_number' }
144
+ )
145
+ expect(validator.valid?).to eq false
146
+ expect(validator.errors).to eq({ phone: 'is not a valid phone number' })
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ActionController::Parameters support' do
4
+ # Mock ActionController::Parameters for testing
5
+ let(:mock_params_class) do
6
+ Class.new do
7
+ attr_reader :data
8
+
9
+ def initialize(data)
10
+ @data = data
11
+ end
12
+
13
+ def [](key)
14
+ @data[key]
15
+ end
16
+
17
+ def keys
18
+ @data.keys
19
+ end
20
+
21
+ def to_unsafe_h
22
+ @data
23
+ end
24
+
25
+ def is_a?(klass)
26
+ return true if klass.name == 'ActionController::Parameters'
27
+ super
28
+ end
29
+
30
+ def class
31
+ OpenStruct.new(name: 'ActionController::Parameters')
32
+ end
33
+ end
34
+ end
35
+
36
+ before do
37
+ # Define ActionController::Parameters constant for testing
38
+ unless defined?(ActionController::Parameters)
39
+ ActionController = Module.new unless defined?(ActionController)
40
+ ActionController.const_set(:Parameters, mock_params_class)
41
+ end
42
+ end
43
+
44
+ it 'should validate ActionController::Parameters objects' do
45
+ params = ActionController::Parameters.new({ name: 'John', age: 30 })
46
+ validations = { name: 'string', age: 'integer' }
47
+
48
+ validator = HashValidator.validate(params, validations)
49
+
50
+ expect(validator.valid?).to be true
51
+ expect(validator.errors).to be_empty
52
+ end
53
+
54
+ it 'should handle nested ActionController::Parameters' do
55
+ nested_params = ActionController::Parameters.new({ theme: 'dark' })
56
+ params = ActionController::Parameters.new({
57
+ name: 'John',
58
+ preferences: nested_params
59
+ })
60
+
61
+ validations = {
62
+ name: 'string',
63
+ preferences: { theme: 'string' }
64
+ }
65
+
66
+ validator = HashValidator.validate(params, validations)
67
+
68
+ expect(validator.valid?).to be true
69
+ expect(validator.errors).to be_empty
70
+ end
71
+
72
+ it 'should validate ActionController::Parameters with our new validators' do
73
+ params = ActionController::Parameters.new({
74
+ name: 'John',
75
+ email: 'john@example.com',
76
+ website: 'https://john.com',
77
+ zip_code: '12345'
78
+ })
79
+
80
+ validations = {
81
+ name: 'alpha',
82
+ email: 'email',
83
+ website: 'url',
84
+ zip_code: 'digits'
85
+ }
86
+
87
+ validator = HashValidator.validate(params, validations)
88
+
89
+ expect(validator.valid?).to be true
90
+ expect(validator.errors).to be_empty
91
+ end
92
+
93
+ it 'should still work with regular hashes' do
94
+ hash = { name: 'John', age: 30 }
95
+ validations = { name: 'string', age: 'integer' }
96
+
97
+ validator = HashValidator.validate(hash, validations)
98
+
99
+ expect(validator.valid?).to be true
100
+ expect(validator.errors).to be_empty
101
+ end
102
+ end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+
3
+ describe HashValidator::Validator::HexColorValidator do
4
+ let(:validator) { HashValidator::Validator::HexColorValidator.new }
5
+
6
+ context 'valid hex colors' do
7
+ it 'validates 6-digit hex colors (lowercase)' do
8
+ errors = {}
9
+ validator.validate('key', '#ff0000', {}, errors)
10
+ expect(errors).to be_empty
11
+ end
12
+
13
+ it 'validates 6-digit hex colors (uppercase)' do
14
+ errors = {}
15
+ validator.validate('key', '#FF0000', {}, errors)
16
+ expect(errors).to be_empty
17
+ end
18
+
19
+ it 'validates 6-digit hex colors (mixed case)' do
20
+ errors = {}
21
+ validator.validate('key', '#Ff0000', {}, errors)
22
+ expect(errors).to be_empty
23
+ end
24
+
25
+ it 'validates 3-digit hex colors (lowercase)' do
26
+ errors = {}
27
+ validator.validate('key', '#f00', {}, errors)
28
+ expect(errors).to be_empty
29
+ end
30
+
31
+ it 'validates 3-digit hex colors (uppercase)' do
32
+ errors = {}
33
+ validator.validate('key', '#F00', {}, errors)
34
+ expect(errors).to be_empty
35
+ end
36
+
37
+ it 'validates black color' do
38
+ errors = {}
39
+ validator.validate('key', '#000000', {}, errors)
40
+ expect(errors).to be_empty
41
+ end
42
+
43
+ it 'validates white color' do
44
+ errors = {}
45
+ validator.validate('key', '#ffffff', {}, errors)
46
+ expect(errors).to be_empty
47
+ end
48
+
49
+ it 'validates complex hex colors' do
50
+ errors = {}
51
+ validator.validate('key', '#3a7bd4', {}, errors)
52
+ expect(errors).to be_empty
53
+ end
54
+ end
55
+
56
+ context 'invalid hex colors' do
57
+ it 'rejects non-string values' do
58
+ errors = {}
59
+ validator.validate('key', 123, {}, errors)
60
+ expect(errors['key']).to eq('is not a valid hex color')
61
+ end
62
+
63
+ it 'rejects nil values' do
64
+ errors = {}
65
+ validator.validate('key', nil, {}, errors)
66
+ expect(errors['key']).to eq('is not a valid hex color')
67
+ end
68
+
69
+ it 'rejects hex colors without hash' do
70
+ errors = {}
71
+ validator.validate('key', 'ff0000', {}, errors)
72
+ expect(errors['key']).to eq('is not a valid hex color')
73
+ end
74
+
75
+ it 'rejects invalid length (4 digits)' do
76
+ errors = {}
77
+ validator.validate('key', '#ff00', {}, errors)
78
+ expect(errors['key']).to eq('is not a valid hex color')
79
+ end
80
+
81
+ it 'rejects invalid length (5 digits)' do
82
+ errors = {}
83
+ validator.validate('key', '#ff000', {}, errors)
84
+ expect(errors['key']).to eq('is not a valid hex color')
85
+ end
86
+
87
+ it 'rejects invalid length (7 digits)' do
88
+ errors = {}
89
+ validator.validate('key', '#ff00000', {}, errors)
90
+ expect(errors['key']).to eq('is not a valid hex color')
91
+ end
92
+
93
+ it 'rejects invalid characters' do
94
+ errors = {}
95
+ validator.validate('key', '#gg0000', {}, errors)
96
+ expect(errors['key']).to eq('is not a valid hex color')
97
+ end
98
+
99
+ it 'rejects empty strings' do
100
+ errors = {}
101
+ validator.validate('key', '', {}, errors)
102
+ expect(errors['key']).to eq('is not a valid hex color')
103
+ end
104
+
105
+ it 'rejects just hash symbol' do
106
+ errors = {}
107
+ validator.validate('key', '#', {}, errors)
108
+ expect(errors['key']).to eq('is not a valid hex color')
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+
3
+ describe HashValidator::Validator::IpValidator do
4
+ let(:validator) { HashValidator::Validator::IpValidator.new }
5
+
6
+ context 'valid IP addresses' do
7
+ it 'validates IPv4 addresses' do
8
+ errors = {}
9
+ validator.validate('key', '192.168.1.1', {}, errors)
10
+ expect(errors).to be_empty
11
+ end
12
+
13
+ it 'validates IPv4 localhost' do
14
+ errors = {}
15
+ validator.validate('key', '127.0.0.1', {}, errors)
16
+ expect(errors).to be_empty
17
+ end
18
+
19
+ it 'validates IPv4 zero address' do
20
+ errors = {}
21
+ validator.validate('key', '0.0.0.0', {}, errors)
22
+ expect(errors).to be_empty
23
+ end
24
+
25
+ it 'validates IPv4 broadcast address' do
26
+ errors = {}
27
+ validator.validate('key', '255.255.255.255', {}, errors)
28
+ expect(errors).to be_empty
29
+ end
30
+
31
+ it 'validates IPv6 addresses' do
32
+ errors = {}
33
+ validator.validate('key', '2001:db8:85a3::8a2e:370:7334', {}, errors)
34
+ expect(errors).to be_empty
35
+ end
36
+
37
+ it 'validates IPv6 localhost' do
38
+ errors = {}
39
+ validator.validate('key', '::1', {}, errors)
40
+ expect(errors).to be_empty
41
+ end
42
+
43
+ it 'validates IPv6 zero address' do
44
+ errors = {}
45
+ validator.validate('key', '::', {}, errors)
46
+ expect(errors).to be_empty
47
+ end
48
+
49
+ it 'validates IPv4-mapped IPv6 addresses' do
50
+ errors = {}
51
+ validator.validate('key', '::ffff:192.168.1.1', {}, errors)
52
+ expect(errors).to be_empty
53
+ end
54
+ end
55
+
56
+ context 'invalid IP addresses' do
57
+ it 'rejects non-string values' do
58
+ errors = {}
59
+ validator.validate('key', 123, {}, errors)
60
+ expect(errors['key']).to eq('is not a valid IP address')
61
+ end
62
+
63
+ it 'rejects nil values' do
64
+ errors = {}
65
+ validator.validate('key', nil, {}, errors)
66
+ expect(errors['key']).to eq('is not a valid IP address')
67
+ end
68
+
69
+ it 'rejects malformed IPv4 addresses' do
70
+ errors = {}
71
+ validator.validate('key', '256.1.1.1', {}, errors)
72
+ expect(errors['key']).to eq('is not a valid IP address')
73
+ end
74
+
75
+ it 'rejects malformed IPv6 addresses' do
76
+ errors = {}
77
+ validator.validate('key', '2001:db8:85a3::8a2e::7334', {}, errors)
78
+ expect(errors['key']).to eq('is not a valid IP address')
79
+ end
80
+
81
+ it 'rejects non-IP strings' do
82
+ errors = {}
83
+ validator.validate('key', 'not an ip', {}, errors)
84
+ expect(errors['key']).to eq('is not a valid IP address')
85
+ end
86
+
87
+ it 'rejects empty strings' do
88
+ errors = {}
89
+ validator.validate('key', '', {}, errors)
90
+ expect(errors['key']).to eq('is not a valid IP address')
91
+ end
92
+
93
+ it 'rejects hostnames' do
94
+ errors = {}
95
+ validator.validate('key', 'example.com', {}, errors)
96
+ expect(errors['key']).to eq('is not a valid IP address')
97
+ end
98
+
99
+ it 'rejects URLs' do
100
+ errors = {}
101
+ validator.validate('key', 'http://192.168.1.1', {}, errors)
102
+ expect(errors['key']).to eq('is not a valid IP address')
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ describe HashValidator::Validator::Ipv4Validator do
4
+ let(:validator) { HashValidator::Validator::Ipv4Validator.new }
5
+
6
+ context 'valid IPv4 addresses' do
7
+ it 'validates standard IPv4 addresses' do
8
+ errors = {}
9
+ validator.validate('key', '192.168.1.1', {}, errors)
10
+ expect(errors).to be_empty
11
+ end
12
+
13
+ it 'validates localhost' do
14
+ errors = {}
15
+ validator.validate('key', '127.0.0.1', {}, errors)
16
+ expect(errors).to be_empty
17
+ end
18
+
19
+ it 'validates zero address' do
20
+ errors = {}
21
+ validator.validate('key', '0.0.0.0', {}, errors)
22
+ expect(errors).to be_empty
23
+ end
24
+
25
+ it 'validates broadcast address' do
26
+ errors = {}
27
+ validator.validate('key', '255.255.255.255', {}, errors)
28
+ expect(errors).to be_empty
29
+ end
30
+
31
+ it 'validates private network addresses' do
32
+ errors = {}
33
+ validator.validate('key', '10.0.0.1', {}, errors)
34
+ expect(errors).to be_empty
35
+ end
36
+
37
+ it 'validates Class B private addresses' do
38
+ errors = {}
39
+ validator.validate('key', '172.16.0.1', {}, errors)
40
+ expect(errors).to be_empty
41
+ end
42
+ end
43
+
44
+ context 'invalid IPv4 addresses' do
45
+ it 'rejects non-string values' do
46
+ errors = {}
47
+ validator.validate('key', 123, {}, errors)
48
+ expect(errors['key']).to eq('is not a valid IPv4 address')
49
+ end
50
+
51
+ it 'rejects nil values' do
52
+ errors = {}
53
+ validator.validate('key', nil, {}, errors)
54
+ expect(errors['key']).to eq('is not a valid IPv4 address')
55
+ end
56
+
57
+ it 'rejects octets over 255' do
58
+ errors = {}
59
+ validator.validate('key', '256.1.1.1', {}, errors)
60
+ expect(errors['key']).to eq('is not a valid IPv4 address')
61
+ end
62
+
63
+ it 'rejects too few octets' do
64
+ errors = {}
65
+ validator.validate('key', '192.168.1', {}, errors)
66
+ expect(errors['key']).to eq('is not a valid IPv4 address')
67
+ end
68
+
69
+ it 'rejects too many octets' do
70
+ errors = {}
71
+ validator.validate('key', '192.168.1.1.1', {}, errors)
72
+ expect(errors['key']).to eq('is not a valid IPv4 address')
73
+ end
74
+
75
+ it 'rejects IPv6 addresses' do
76
+ errors = {}
77
+ validator.validate('key', '2001:db8::1', {}, errors)
78
+ expect(errors['key']).to eq('is not a valid IPv4 address')
79
+ end
80
+
81
+ it 'rejects malformed addresses' do
82
+ errors = {}
83
+ validator.validate('key', '192.168.1.', {}, errors)
84
+ expect(errors['key']).to eq('is not a valid IPv4 address')
85
+ end
86
+
87
+ it 'rejects empty strings' do
88
+ errors = {}
89
+ validator.validate('key', '', {}, errors)
90
+ expect(errors['key']).to eq('is not a valid IPv4 address')
91
+ end
92
+
93
+ it 'rejects non-numeric octets' do
94
+ errors = {}
95
+ validator.validate('key', '192.168.a.1', {}, errors)
96
+ expect(errors['key']).to eq('is not a valid IPv4 address')
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ describe HashValidator::Validator::Ipv6Validator do
4
+ let(:validator) { HashValidator::Validator::Ipv6Validator.new }
5
+
6
+ context 'valid IPv6 addresses' do
7
+ it 'validates full IPv6 addresses' do
8
+ errors = {}
9
+ validator.validate('key', '2001:0db8:85a3:0000:0000:8a2e:0370:7334', {}, errors)
10
+ expect(errors).to be_empty
11
+ end
12
+
13
+ it 'validates compressed IPv6 addresses' do
14
+ errors = {}
15
+ validator.validate('key', '2001:db8:85a3::8a2e:370:7334', {}, errors)
16
+ expect(errors).to be_empty
17
+ end
18
+
19
+ it 'validates IPv6 localhost' do
20
+ errors = {}
21
+ validator.validate('key', '::1', {}, errors)
22
+ expect(errors).to be_empty
23
+ end
24
+
25
+ it 'validates IPv6 zero address' do
26
+ errors = {}
27
+ validator.validate('key', '::', {}, errors)
28
+ expect(errors).to be_empty
29
+ end
30
+
31
+ it 'validates IPv6 with leading zeros' do
32
+ errors = {}
33
+ validator.validate('key', '2001:0db8::0001', {}, errors)
34
+ expect(errors).to be_empty
35
+ end
36
+
37
+ it 'validates IPv6 with mixed case' do
38
+ errors = {}
39
+ validator.validate('key', '2001:DB8::1', {}, errors)
40
+ expect(errors).to be_empty
41
+ end
42
+
43
+ it 'validates IPv4-mapped IPv6 addresses' do
44
+ errors = {}
45
+ validator.validate('key', '::ffff:192.168.1.1', {}, errors)
46
+ expect(errors).to be_empty
47
+ end
48
+ end
49
+
50
+ context 'invalid IPv6 addresses' do
51
+ it 'rejects non-string values' do
52
+ errors = {}
53
+ validator.validate('key', 123, {}, errors)
54
+ expect(errors['key']).to eq('is not a valid IPv6 address')
55
+ end
56
+
57
+ it 'rejects nil values' do
58
+ errors = {}
59
+ validator.validate('key', nil, {}, errors)
60
+ expect(errors['key']).to eq('is not a valid IPv6 address')
61
+ end
62
+
63
+ it 'rejects IPv4 addresses' do
64
+ errors = {}
65
+ validator.validate('key', '192.168.1.1', {}, errors)
66
+ expect(errors['key']).to eq('is not a valid IPv6 address')
67
+ end
68
+
69
+ it 'rejects malformed IPv6 addresses' do
70
+ errors = {}
71
+ validator.validate('key', '2001:db8:85a3::8a2e::7334', {}, errors)
72
+ expect(errors['key']).to eq('is not a valid IPv6 address')
73
+ end
74
+
75
+ it 'rejects too many groups' do
76
+ errors = {}
77
+ validator.validate('key', '2001:0db8:85a3:0000:0000:8a2e:0370:7334:extra', {}, errors)
78
+ expect(errors['key']).to eq('is not a valid IPv6 address')
79
+ end
80
+
81
+ it 'rejects invalid characters' do
82
+ errors = {}
83
+ validator.validate('key', '2001:db8:85a3::8a2g:370:7334', {}, errors)
84
+ expect(errors['key']).to eq('is not a valid IPv6 address')
85
+ end
86
+
87
+ it 'rejects empty strings' do
88
+ errors = {}
89
+ validator.validate('key', '', {}, errors)
90
+ expect(errors['key']).to eq('is not a valid IPv6 address')
91
+ end
92
+
93
+ it 'rejects incomplete addresses' do
94
+ errors = {}
95
+ validator.validate('key', '2001:db8:', {}, errors)
96
+ expect(errors['key']).to eq('is not a valid IPv6 address')
97
+ end
98
+ end
99
+ end