Add daily BTC/USD endpoint with historical caching and Swagger UI
pr / test (pull_request) Successful in 58s
pr / test (pull_request) Successful in 58s
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
(ns pmagnus.btcdata.kraken.day-test
|
||||
(:require [clojure.test :refer [deftest is]]
|
||||
[pmagnus.btcdata.kraken.day :as day])
|
||||
(:import [java.time LocalDate ZoneOffset]))
|
||||
|
||||
(def date (LocalDate/parse "2020-01-01"))
|
||||
(def ts (.toEpochSecond (.atStartOfDay date) ZoneOffset/UTC))
|
||||
|
||||
(defn database [store writes]
|
||||
(fn [q p]
|
||||
(case q
|
||||
:get-kraken-day-by-ts (get @store [:recent (:ts p)])
|
||||
:get-kraken-day-history-by-ts (get @store [:history (:ts p)])
|
||||
(:upsert-kraken-day! :upsert-kraken-day-history!)
|
||||
(let [table (if (= q :upsert-kraken-day!) :recent :history)]
|
||||
(swap! writes conj q)
|
||||
(swap! store assoc [table (:ts p)]
|
||||
(-> p (assoc :trade_count (:trade-count p)) (dissoc :trade-count)))))))
|
||||
|
||||
(deftest cached-data-never-fetches
|
||||
(doseq [table [:recent :history]]
|
||||
(let [row {:ts ts} store (atom {[table ts] row})]
|
||||
(with-redefs [day/request! (fn [_] (throw (Exception. "Unexpected fetch")))]
|
||||
(is (= row (day/find-day! (database store (atom [])) date)))))))
|
||||
|
||||
(deftest recent-candle-is-persisted
|
||||
(let [store (atom {}) writes (atom []) q (database store writes)]
|
||||
(with-redefs [day/request! (fn [_] {:XXBTZUSD [[ts "10" "12" "9" "11" "10.5" "2" 3]]})]
|
||||
(is (= 11M (:close (day/find-day! q date))))
|
||||
(is (= [:upsert-kraken-day!] @writes)))))
|
||||
|
||||
(deftest historical-pagination-and-cache
|
||||
(let [store (atom {}) writes (atom []) q (database store writes)
|
||||
calls (atom []) cursor (str (*' (+ ts 5) 1000000000))]
|
||||
(with-redefs [day/pause! (fn [])
|
||||
day/request!
|
||||
(fn [url]
|
||||
(swap! calls conj url)
|
||||
(case (count @calls)
|
||||
1 {:XXBTZUSD [[(+ ts 86400) "1" "1" "1" "1" "1" "1" 1]]}
|
||||
2 {:XXBTZUSD [["10" "2" ts] ["20" "1" (+ ts 5)]] :last cursor}
|
||||
3 {:XXBTZUSD [["5" "1" (+ ts 86399.9M)]
|
||||
["999" "1" (+ ts 86400)]] :last "unused"}))]
|
||||
(let [result (day/find-day! q date)]
|
||||
(is (= {:open 10M :high 20M :low 5M :close 5M :volume 4M
|
||||
:vwap 11.25000000M :trade_count 3 :ts ts} result))
|
||||
(is (= result (day/find-day! q date)))
|
||||
(is (= 3 (count @calls)))
|
||||
(is (.endsWith (last @calls) cursor))
|
||||
(is (= [:upsert-kraken-day-history!] @writes))))))
|
||||
|
||||
(deftest failed-or-empty-history-is-not-persisted
|
||||
(doseq [result [{:XXBTZUSD [] :last "0"}
|
||||
{:XXBTZUSD [["1" "1" ts]] :last "0"}
|
||||
{:wrong []}]]
|
||||
(let [writes (atom []) calls (atom 0)]
|
||||
(with-redefs [day/request! (fn [_]
|
||||
(if (= 1 (swap! calls inc))
|
||||
{:XXBTZUSD [[(+ ts 86400)]]} result))]
|
||||
(if (= [] (:XXBTZUSD result))
|
||||
(is (nil? (day/find-day! (database (atom {}) writes) date)))
|
||||
(is (thrown? clojure.lang.ExceptionInfo
|
||||
(day/find-day! (database (atom {}) writes) date))))
|
||||
(is (empty? @writes))))))
|
||||
|
||||
(deftest upstream-failure-does-not-save-a-partial-day
|
||||
(let [writes (atom []) calls (atom 0)]
|
||||
(with-redefs [day/pause! (fn [])
|
||||
day/request! (fn [_]
|
||||
(case (swap! calls inc)
|
||||
1 {:XXBTZUSD [[(+ ts 86400)]]}
|
||||
2 {:XXBTZUSD [["10" "1" ts]]
|
||||
:last (str (*' (inc ts) 1000000000))}
|
||||
(throw (Exception. "Connection failed"))))]
|
||||
(is (thrown? clojure.lang.ExceptionInfo
|
||||
(day/find-day! (database (atom {}) writes) date)))
|
||||
(is (empty? @writes)))))
|
||||
@@ -0,0 +1,54 @@
|
||||
(ns pmagnus.btcdata.web.controllers.day-test
|
||||
(:require [clojure.test :refer [deftest is]]
|
||||
[clojure.data.json :as json]
|
||||
[integrant.core :as ig]
|
||||
[pmagnus.btcdata.kraken.day :as day]
|
||||
[pmagnus.btcdata.web.controllers.day :as controller]
|
||||
[pmagnus.btcdata.web.routes.api :as api]
|
||||
[pmagnus.btcdata.web.handler]
|
||||
[reitit.ring :as ring]))
|
||||
|
||||
(def app
|
||||
(ring/ring-handler
|
||||
(ring/router [(ig/init-key :reitit.routes/api {:base-path "/api" :query-fn nil})])))
|
||||
|
||||
(defn request [uri query]
|
||||
(app {:request-method :get :uri uri :query-string query
|
||||
:headers {"accept" "application/json"}}))
|
||||
|
||||
(deftest routes-and-numeric-json
|
||||
(with-redefs [day/find-day! (fn [_ _]
|
||||
{:open 10M :high 12M :low 9M :close 11M
|
||||
:vwap 10.5M :volume 2M :trade_count 3})]
|
||||
(doseq [[uri query] [["/api/price/day/2020-01-01" nil]
|
||||
["/api/price/day" "date=2020-01-01"]]]
|
||||
(let [r (request uri query) body (json/read-str (slurp (:body r)) :key-fn keyword)]
|
||||
(is (= 200 (:status r)))
|
||||
(is (= "BTC/USD" (:pair body)))
|
||||
(is (= "2020-01-01" (:date body)))
|
||||
(is (number? (:open body)))))))
|
||||
|
||||
(deftest invalid-dates
|
||||
(doseq [s [nil "bad" "2020-02-30" "2020-1-01" "9999-01-01"
|
||||
(str (java.time.LocalDate/now java.time.ZoneOffset/UTC))]]
|
||||
(with-redefs [day/find-day! (fn [& _] (throw (Exception. "Must not fetch")))]
|
||||
(is (= 400 (:status (controller/handle {} {:query-params {"date" s}})))))))
|
||||
|
||||
(deftest error-responses
|
||||
(with-redefs [day/find-day! (constantly nil)]
|
||||
(is (= 404 (:status (request "/api/price/day/2020-01-01" nil)))))
|
||||
(with-redefs [day/find-day! (fn [& _] (throw (ex-info "upstream" {:type ::day/upstream})))]
|
||||
(is (= 502 (:status (request "/api/price/day/2020-01-01" nil))))))
|
||||
|
||||
(deftest swagger-documents-both-routes
|
||||
(let [r (request "/api/swagger.json" nil)
|
||||
body (json/read-str (slurp (:body r)))]
|
||||
(is (= 200 (:status r)))
|
||||
(is (get-in body ["paths" "/api/price/day" "get"]))
|
||||
(is (get-in body ["paths" "/api/price/day/{date}" "get"]))))
|
||||
|
||||
(deftest swagger-ui-is-served
|
||||
(let [handler (ig/init-key :handler/ring
|
||||
{:router #(ring/router []) :api-path "/api"})]
|
||||
(is (= 302 (:status (handler {:request-method :get :uri "/swagger" :headers {}}))))
|
||||
(is (= 200 (:status (handler {:request-method :get :uri "/swagger/" :headers {}}))))))
|
||||
Reference in New Issue
Block a user