Skip to content

Instantly share code, notes, and snippets.

@prepor
Created March 4, 2016 12:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save prepor/50ac0bd32b6a6c95daac to your computer and use it in GitHub Desktop.
Save prepor/50ac0bd32b6a6c95daac to your computer and use it in GitHub Desktop.
(ns flock.staff.test.fixtures
(:require [clojure.java.jdbc :as jdbc]
[clojure.string :as str]
[com.stuartsierra.component :as component]
[defcomponent :refer [defcomponent]]
[flock.staff.components :as components]
[flock.staff.time :as time]
[flock.staff.utils :as utils]
[plumbing.core :refer :all]
[slingshot.slingshot :refer [throw+]]))
(defn generate-string
([] (generate-string 8))
([n] (let [chars (map char (concat (range 65 91) (range 97 123)))
chars* (take n (repeatedly #(rand-nth chars)))]
(apply str chars*))))
(defn generate-email
[]
(str/lower-case (str (generate-string 8) "@" (generate-string 8) ".ru")))
(defn generate-name
[]
(let [first-name (-> (generate-string 10) str/lower-case str/capitalize)
second-name (-> (generate-string 8) str/lower-case str/capitalize)]
(str first-name " " second-name)))
;; Фикстуры определяются следующим синтаксисом:
;; {:fixture-name [:db? :collection fixture-fn]}
;; :db по-умолчанию это :main-db.
;; fixture-fn это fnk где в параметрах определены фикстуры, от которых зависит текущая
;; при запросе на создание фикстуры будут сначала созданы фикстуры от которых
;; она зависит. уже созданные фикстуры кешируются по ключу, с которым они
;; запрашивались
;; fixture-fn возвращает мап, где если значение это функция, то она будет
;; вызвана (динамический параметры)
;; Создание фикстур просиходит следующим образом:
;; (make fixtures & specs)
;; где spec:
;; - keyword тогда будет созданна одноименная фикстура
;; - map {:fixture :fixture-name :as :fixture-key :merge {:title "hello} :depends {:site :new-site}}
;; Описание параметров map:
;; - :fixture обязательный параметр, "базовая" фикстура, единственный обязательный аргумент
;; - :as ключ с которым будет создана фикстура (возвращена с ним, закеширована и доступна для зависимостей)
;; - :merge параметры будут смержаны с теми, что вернет fixture-fn
;; - :depends определяет из каких именно ключей нужно брать зависимости для
;; фикстуры. Так, например, если фикстура зависит от site, то по-умолчанию,
;; будет искаться именно фикстура с ключем :site. Пример, наверное, будет
;; понятнее:
;; (make fixtures :site :campaign
;; {:fixture :site :as :new-site :merge {:title "good site!}}
;; {:fixture :campaign :as :new-campaign :depends {:site :new-site}})
;; Этот вызов вернет мэп с 4 элементами: :site, :campaign, :new-site
;; и :new-campaign, при этом :campaign будет создана с :site, а :new-campaign
;; с :new-site
;;
;; Все созданные инстансы кешируются в компоненте, т.е., при рекомендуемом
;; ипсользовании (старте / стопе в use-fixtures тестов), будут закешированы в
;; рамках одного теста (а не одного вызова make, как можно было бы подумать)
(def repository
{:widget [:widgets
(fnk [site]
{:site_id (:id site)
:template nil
:type "Widget"
:filename "default"
:render_type nil
:created_at #(time/->sql-time)
:updated_at #(time/->sql-time)})]
:site [:sites
(fnk [country]
{:domain "some-domain"
:account_id 1
:url "some-url"
:currency "rub"
:billing_enabled_at (time/->sql-time "2015-01-01")
:company_size 1
:country_id (:id country)
:token #(generate-string 16)
:created_at #(time/->sql-time)
:updated_at #(time/->sql-time)})]
:country [:countries
(fnk []
{:name "Russia"
:other_name "Russian Federation"
:locale "ru"
:currency "rur"
:timezone "Moscow"
:country_code "RU"
:created_at #(time/->sql-time)
:updated_at #(time/->sql-time)})]
:offer [:offers
(fnk [campaign site customer {parent-order nil}]
{:campaign_id (:id campaign)
:customer_id (:id customer)
:parent_order_id (:id parent-order)
:token #(generate-string 16)
:secret_token #(generate-string 16)
:site_id (:id site)
:created_at #(time/->sql-time)})]
:campaign [:campaigns
(fnk [site {coupon-source nil} {customer-segment nil} {campaigns-group nil}]
{:site_id (:id site)
:campaign_type "postcheckout"
:title "Test Campaign"
:state "online"
:landing_page_type ""
:friend_coupon_source_id (:id coupon-source)
:friend_reward_method "coupon"
:friend_reward_value 1000
:friend_reward_unit "currency"
:publisher_reward_limit_type ""
:token #(generate-string 16)
:customer_segment_id (:id customer-segment)
:campaign_group_id (:id campaigns-group)})]
:campaigns-group [:campaigns_groups
(fnk [site]
{:site_id (:id site)
:state "online"
:created_at #(time/->sql-time)
:updated_at #(time/->sql-time)})]
:campaign-motivation [:campaign_motivations
(fnk [campaign motivation]
{:campaign_id (:id campaign)
:motivation_id (:id motivation)
:purpose nil})]
:customer [:customers
(fnk [{location nil}]
{:email #(generate-email)
:name #(generate-name)
:location_id (:id location)})]
:xname-customer [:customers
(fnk []
{:email "xname@flocktory.com"
:name "XNAME"})]
:customer-segment [:customer_segments
(fnk [site]
{:site_id (:id site)
:locations {}})]
:customer-stat [:exchange_customer_stats
(fnk [site customer]
{:customer_id (:id customer)
:site_id (:id site)
:last_buy_at #(time/->sql-time)
:created_at #(time/->sql-time)
:updated_at #(time/->sql-time)})]
:order [:orders
(fnk [site customer]
{:site_id (:id site)
:price 100.00
:customer_id (:id customer)
:order_id #(generate-string 10)
:confirmation_state "new"
:created_at #(time/->sql-time)})]
:reward [:rewards
(fnk [customer campaign]
{:customer_id (:id customer)
:campaign_id (:id campaign)
:type "Reward::Type"
:state "new"})]
:banner-session [:exchange_banner_sessions
(fnk [site customer]
{:source_site_id (:id site)
:customer_id (:id customer)
:token #(generate-string 16)
:created_at #(time/->sql-time)
:updated_at #(time/->sql-time)})]
:campaign-widget [:campaign_widgets
(fnk [campaign widget]
{:campaign_id (:id campaign)
:widget_id (:id widget)
:created_at (fn [v] (time/->sql-time))
:updated_at (time/->sql-time)})]
:motivation [:motivations
(fnk [site {coupon-source nil}]
{:site_id (:id site)
:coupon_source_id (:id coupon-source)
:type (if coupon-source "coupon" "exchange")
:value "10"
:unit "%"
:created_at #(time/->sql-time)
:updated_at #(time/->sql-time)})]
:rate-limit [:rate_limits
(fnk [site]
{:site_id (:id site)
:unit "daily"
:value 1
:is_active true
:scope {"product" "xmail"
"site" (-> site :id str)
"profile" "*"}
:created_at #(time/->sql-time)
:updated_at #(time/->sql-time)})]
:coupon-source [:coupon_sources
(fnk [site]
{:site_id (:id site)
:type "CouponSource::MultiUse"
:coupon_code "COUPON-CODE"
:unit "%"
:value "10"})]
:coupon [:coupons
(fnk [coupon-source]
{:coupon_source_id (:id coupon-source)
:code #(generate-string 10)
:state "unused"})]
:site-inactivity [:site_inactivities
(fnk [site]
{:site_id (:id site)
:inactivity (* 10 60)
:created_at #(time/->sql-time)
:updated_at #(time/->sql-time)})]
:utm-channel [:utm_channels
(fnk [site]
{:site_id (:id site)
:title "Utm Channel"
:created_at #(time/->sql-time)
:updated_at #(time/->sql-time)})]
:utm-channel-segment [:utm_channel_segments
(fnk [customer-segment utm-channel]
{:customer_segment_id (:id customer-segment)
:utm_channel_id (:id utm-channel)
:created_at #(time/->sql-time)
:updated_at #(time/->sql-time)
:match true})]
:new-widget [:widgets-db :widgets
(fnk [site]
{:id utils/uuid
:type "PrecheckoutGeneral"
:meta {:description "Good widget"
:site (str (:id site))}})]
:widget-version [:widgets-db :versions
(fnk [new-widget]
(let [content "Greate widget {{publisher_name}}"]
{:id utils/uuid
:content content
:widget_id (:id new-widget)
:sha (utils/sha content)}))]
:widget-version-attach [:widgets-db :usages
(fnk [widget-version campaign]
{:version_id (:id widget-version)
:key "campaign"
:value (str (:id campaign))
:meta {"spot" "bottom"
"colors_json" "{\"title\":\"black\",\"background\":\"white\"}"}})]
:location [:locations
(fnk []
{:city "Moscow"
:region "Moscow"
:country_code "RU"})]})
(defn normalize-repository
[repository]
(for-map [[k v] repository
:let [[db-key table f] (if (= 2 (count v))
(cons :main-db v)
v)
all-deps (utils/fnk-to-deps f)
deps (filter #(not (:optional (meta %))) all-deps)
optional (filter #(:optional (meta %)) all-deps)]]
k {:key k
:db-key db-key
:table table
:f f
:depends (for-map [d deps] (first d) (first d))
:optional-depends (for-map [d optional] (first d) (first d))}))
(def normalized-repository (normalize-repository repository))
(defn evaluate-res
[m]
(map-vals #(if (fn? %) (%) %) m))
(defn normalize-spec
[normalized-repo spec]
(if (keyword? spec)
(normalized-repo spec)
(let [based-on (normalized-repo (:fixture spec))]
(when-not based-on
(throw+ {:type ::unknown-fixture :key (:fixture spec)}))
{:key (:as spec (:fixture spec))
:depends (merge (:depends based-on) (:depends spec))
:optional-depends (merge (:optional-depends based-on) (:depends spec))
:db-key (:db-key based-on)
:table (:table based-on)
:f (fn [m]
(merge ((:f based-on) m) (:merge spec)))})))
(defn make-one!
[fixtures repository spec]
(locking repository
(let [db ((:db-key spec) fixtures)
cache (:cache fixtures)]
(if-let [exists (get @cache (:key spec))]
exists
(let [deps-map (for-map [[d source] (:depends spec)]
d (when-let [spec (repository source)]
(make-one! fixtures repository spec)
;; (throw+ {:type ::unknown-dep :key source})
))
optionals (for-map [[d source] (:optional-depends spec)
:let [v (get @cache source)]]
d v)
data (evaluate-res ((:f spec) (merge deps-map optionals)))
data* (first (jdbc/insert! db (:table spec) data))]
(swap! cache assoc (:key spec) data*)
data*)))))
(defn make
[fixtures & specs]
(let [normalized (map #(normalize-spec (:repository fixtures) %) specs)
full-repository (reduce #(assoc %1 (:key %2) %2)
(:repository fixtures) normalized)]
(for-map [s normalized]
(:key s) (make-one! fixtures full-repository s))))
(defcomponent fixtures [components/main-db]
[_]
(start [this]
(assoc this :cache (atom {})
:repository (normalize-repository repository)))
(stop [this] this))
(defn mk
[& [r]]
(fixtures {:repo r}))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment