File: override_validators_spec.rb

package info (click to toggle)
ruby-contracts 0.17-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 624 kB
  • sloc: ruby: 3,805; makefile: 4; sh: 2
file content (162 lines) | stat: -rw-r--r-- 3,931 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
RSpec.describe Contract do
  describe ".override_validator" do
    around do |example|
      Contract.reset_validators
      example.run
      Contract.reset_validators
    end

    it "allows to override simple validators" do
      Contract.override_validator(Hash) do |contract|
        lambda do |arg|
          return false unless arg.is_a?(Hash)
          # Any hash in my system should have :it_is_a_hash key!
          return false unless arg.key?(:it_is_a_hash)
          contract.keys.all? do |k|
            Contract.valid?(arg[k], contract[k])
          end
        end
      end

      klass = Class.new do
        include Contracts::Core

        Contract ({ :a => Contracts::Num, :b => String }) => nil
        def something(opts)
          nil
        end
      end

      obj = klass.new

      expect do
        obj.something({ :a => 35, :b => "hello" })
      end.to raise_error(ContractError)

      expect do
        obj.something({
          :a => 35,
          :b => "hello",
          :it_is_a_hash => true
        })
      end.not_to raise_error
    end

    it "allows to override valid contract" do
      Contract.override_validator(:valid) do |contract|
        if contract.respond_to?(:in_valid_state?)
          lambda do |arg|
            contract.in_valid_state? && contract.valid?(arg)
          end
        else
          lambda { |arg| contract.valid?(arg) }
        end
      end

      stateful_contract = Class.new(Contracts::CallableClass) do
        def initialize(contract)
          @contract = contract
          @state = 0
        end

        def in_valid_state?
          @state < 3
        end

        def valid?(arg)
          @state += 1
          Contract.valid?(arg, @contract)
        end
      end

      klass = Class.new do
        include Contracts::Core

        Contract stateful_contract[Contracts::Num] => Contracts::Num
        def only_three_times(x)
          x * x
        end
      end

      obj = klass.new

      expect(obj.only_three_times(3)).to eq(9)
      expect(obj.only_three_times(3)).to eq(9)
      expect(obj.only_three_times(3)).to eq(9)

      expect do
        obj.only_three_times(3)
      end.to raise_error(ContractError)

      expect do
        obj.only_three_times(3)
      end.to raise_error(ContractError)
    end

    it "allows to override class validator" do
      # Make contracts accept all rspec doubles
      Contract.override_validator(:class) do |contract|
        lambda do |arg|
          arg.is_a?(RSpec::Mocks::Double) ||
            arg.is_a?(contract)
        end
      end

      klass = Class.new do
        include Contracts::Core

        Contract String => String
        def greet(name)
          "hello, #{name}"
        end
      end

      obj = klass.new

      expect(obj.greet("world")).to eq("hello, world")

      expect do
        obj.greet(4)
      end.to raise_error(ContractError)

      expect(obj.greet(double("name"))).to match(
        /hello, #\[.*Double.*"name"\]/
      )
    end

    it "allows to override default validator" do
      spy = double("spy")

      Contract.override_validator(:default) do |contract|
        lambda do |arg|
          spy.log("#{arg} == #{contract}")
          arg == contract
        end
      end

      klass = Class.new do
        include Contracts::Core

        Contract 1, Contracts::Num => Contracts::Num
        def gcd(_, b)
          b
        end

        Contract Contracts::Num, Contracts::Num => Contracts::Num
        def gcd(a, b)
          gcd(b % a, a)
        end
      end

      obj = klass.new

      expect(spy).to receive(:log).with("8 == 1").ordered.once
      expect(spy).to receive(:log).with("5 == 1").ordered.once
      expect(spy).to receive(:log).with("3 == 1").ordered.once
      expect(spy).to receive(:log).with("2 == 1").ordered.once
      expect(spy).to receive(:log).with("1 == 1").ordered.once

      obj.gcd(8, 5)
    end
  end
end