clj / test / ird / spec_check_test.clj
445 words
 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
(ns ird.spec-check-test
  "Offline tests for the conformance validator itself. ird.conformance-test
  only proves the live API agrees with the spec if this detector actually
  detects — a checker that always returns [] would pass that sweep silently
  forever, so each class of drift it is meant to catch is pinned here."
  (:require
   [clojure.test :refer [deftest is testing]]
   [ird.spec-check :as spec-check]))

(defn- check [schema value] (spec-check/check schema value ""))

(def ^:private pin-row
  {"type" "object"
   "additionalProperties" false
   "required" ["id" "cid" "state"]
   "properties" {"id" {"type" "string"}
                 "cid" {"oneOf" [{"type" "string"} {"type" "null"}]}
                 "state" {"type" "string" "enum" ["queued" "pinned" "failed"]}
                 "size_bytes" {"type" "integer"}}})

(deftest conforming-responses-produce-no-problems
  (is (= [] (check pin-row {:id "p1" :cid "bafy1" :state "pinned" :size_bytes 56})))
  (testing "an optional field may be absent, and a oneOf null branch is fine"
    (is (= [] (check pin-row {:id "p1" :cid nil :state "queued"}))))
  (testing "an unknown schema passes rather than guessing"
    (is (= [] (check nil {:anything true})))
    (is (= [] (check {} {:anything true})))))

(deftest detects-every-class-of-drift
  (testing "a required key the API did not return"
    (is (re-find #"\.state is required by the spec but absent"
                 (first (check pin-row {:id "p1" :cid "bafy1"})))))
  (testing "a field the API returns that the spec does not declare"
    ;; the class that found registrar and target_url
    (is (re-find #"\.cid_v1 is returned by the API but absent from the spec"
                 (first (check pin-row {:id "p1" :cid "b" :state "pinned"
                                        :cid_v1 "bafy2"})))))
  (testing "a value outside a declared enum"
    ;; the class that found change-rdns
    (is (re-find #"not in the declared enum"
                 (first (check pin-row {:id "p1" :cid "b" :state "deleting"})))))
  (testing "a declared type the API contradicts"
    ;; the class that would catch a price sent as a number instead of a string
    (is (re-find #"declared integer but the API returned string"
                 (first (check pin-row {:id "p1" :cid "b" :state "pinned"
                                        :size_bytes "56"})))))
  (testing "a oneOf none of whose branches match"
    (is (re-find #"matches none of oneOf"
                 (first (check pin-row {:id "p1" :cid 42 :state "pinned"}))))))

(deftest walks-nested-bodies-and-every-array-row
  (let [page {"type" "object"
              "properties" {"pins" {"type" "array" "items" pin-row}}}]
    (testing "problems are reported with the path that locates them"
      (is (= [".pins[1].state is required by the spec but absent from the response"]
             (check page {:pins [{:id "a" :cid "x" :state "pinned"}
                                 {:id "b" :cid "y"}]}))))
    (testing "a late row is checked, not just the first few"
      ;; change-rdns first appeared at row 7 of the activity feed; sampling
      ;; the head of the list is exactly how that stayed hidden
      (let [rows (concat (repeat 20 {:id "a" :cid "x" :state "pinned"})
                         [{:id "z" :cid "x" :state "not-a-state"}])]
        (is (= 1 (count (check page {:pins (vec rows)}))))
        (is (re-find #"\.pins\[20\]\.state" (first (check page {:pins (vec rows)}))))))))

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/clj.git