|
1011 | 1011 | (process-event [:re-frame.query/ensure-query :books/list {}]) |
1012 | 1012 | (is (zero? @call-count) |
1013 | 1013 | "no effect — query without stale-time-ms never auto-stales"))))) |
| 1014 | + |
| 1015 | +;; --------------------------------------------------------------------------- |
| 1016 | +;; Error recovery cycle tests |
| 1017 | +;; --------------------------------------------------------------------------- |
| 1018 | + |
| 1019 | +(deftest query-error-then-success-clears-error |
| 1020 | + (testing "After a failed query, a successful retry clears the error and restores :success" |
| 1021 | + (rfq/reg-query :books/list {:query-fn (fn [_] {})}) |
| 1022 | + (let [qid (util/query-id :books/list {})] |
| 1023 | + ;; Initial failure |
| 1024 | + (process-event [:re-frame.query/query-failure :books/list {} {:status 500 :body "Internal Server Error"}]) |
| 1025 | + (is (= :error (get-in (app-db) [:re-frame.query/queries qid :status]))) |
| 1026 | + (is (= {:status 500 :body "Internal Server Error"} |
| 1027 | + (get-in (app-db) [:re-frame.query/queries qid :error]))) |
| 1028 | + ;; Retry succeeds |
| 1029 | + (process-event [:re-frame.query/query-success :books/list {} [{:id 1}]]) |
| 1030 | + (let [query (get-in (app-db) [:re-frame.query/queries qid])] |
| 1031 | + (is (= :success (:status query)) |
| 1032 | + "status transitions to :success") |
| 1033 | + (is (= [{:id 1}] (:data query)) |
| 1034 | + "data is stored") |
| 1035 | + (is (nil? (:error query)) |
| 1036 | + "error is cleared on success") |
| 1037 | + (is (false? (:fetching? query))))))) |
| 1038 | + |
| 1039 | +(deftest query-repeated-failures-update-error |
| 1040 | + (testing "Each failure updates the error value, not accumulates" |
| 1041 | + (rfq/reg-query :books/list {:query-fn (fn [_] {})}) |
| 1042 | + (let [qid (util/query-id :books/list {})] |
| 1043 | + ;; First failure |
| 1044 | + (process-event [:re-frame.query/query-failure :books/list {} {:status 500}]) |
| 1045 | + (is (= {:status 500} (get-in (app-db) [:re-frame.query/queries qid :error]))) |
| 1046 | + ;; Second failure with different error |
| 1047 | + (process-event [:re-frame.query/query-failure :books/list {} {:status 503}]) |
| 1048 | + (is (= {:status 503} (get-in (app-db) [:re-frame.query/queries qid :error])) |
| 1049 | + "error is replaced, not accumulated") |
| 1050 | + (is (= :error (get-in (app-db) [:re-frame.query/queries qid :status])))))) |
| 1051 | + |
| 1052 | +(deftest query-success-then-failure-preserves-data |
| 1053 | + (testing "After success then failure, data persists but status is :error" |
| 1054 | + (rfq/reg-query :books/list {:query-fn (fn [_] {})}) |
| 1055 | + (let [qid (util/query-id :books/list {})] |
| 1056 | + ;; Success first |
| 1057 | + (process-event [:re-frame.query/query-success :books/list {} [{:id 1}]]) |
| 1058 | + (is (= :success (get-in (app-db) [:re-frame.query/queries qid :status]))) |
| 1059 | + ;; Then a refetch fails |
| 1060 | + (process-event [:re-frame.query/query-failure :books/list {} {:status 502}]) |
| 1061 | + (let [query (get-in (app-db) [:re-frame.query/queries qid])] |
| 1062 | + (is (= :error (:status query)) |
| 1063 | + "status is :error") |
| 1064 | + (is (= {:status 502} (:error query)) |
| 1065 | + "error is stored") |
| 1066 | + (is (= [{:id 1}] (:data query)) |
| 1067 | + "previous data is preserved across failure"))))) |
| 1068 | + |
| 1069 | +(deftest query-full-error-recovery-cycle |
| 1070 | + (testing "Full cycle: success → stale → refetch fails → retry → success with new data" |
| 1071 | + (let [call-count (atom 0)] |
| 1072 | + (rf/reg-fx :test-http (fn [_] (swap! call-count inc))) |
| 1073 | + (rfq/set-default-effect-fn! |
| 1074 | + (fn [request on-success on-failure] |
| 1075 | + {:test-http (assoc request |
| 1076 | + :on-success on-success |
| 1077 | + :on-failure on-failure)})) |
| 1078 | + (rfq/reg-query :books/list |
| 1079 | + {:query-fn (fn [_] {:method :get :url "/api/books"}) |
| 1080 | + :stale-time-ms 1000}) |
| 1081 | + (let [qid (util/query-id :books/list {})] |
| 1082 | + ;; 1. Initial success |
| 1083 | + (process-event [:re-frame.query/query-success :books/list {} [{:id 1}]]) |
| 1084 | + (is (= :success (get-in (app-db) [:re-frame.query/queries qid :status]))) |
| 1085 | + ;; 2. Make stale |
| 1086 | + (swap! rf-db/app-db assoc-in [:re-frame.query/queries qid :fetched-at] 0) |
| 1087 | + (reset! call-count 0) |
| 1088 | + ;; 3. ensure-query detects staleness → refetch |
| 1089 | + (process-event [:re-frame.query/ensure-query :books/list {}]) |
| 1090 | + (is (= 1 @call-count)) |
| 1091 | + ;; 4. Refetch fails |
| 1092 | + (process-event [:re-frame.query/query-failure :books/list {} {:status 500}]) |
| 1093 | + (is (= :error (get-in (app-db) [:re-frame.query/queries qid :status]))) |
| 1094 | + (is (= [{:id 1}] (get-in (app-db) [:re-frame.query/queries qid :data])) |
| 1095 | + "stale data preserved after failure") |
| 1096 | + ;; 5. Retry — error status is stale, so ensure-query triggers |
| 1097 | + (reset! call-count 0) |
| 1098 | + (process-event [:re-frame.query/ensure-query :books/list {}]) |
| 1099 | + (is (= 1 @call-count) "retry fires because error status is stale") |
| 1100 | + (is (= :loading (get-in (app-db) [:re-frame.query/queries qid :status])) |
| 1101 | + "status resets to :loading on retry after error") |
| 1102 | + ;; 6. Success with new data |
| 1103 | + (process-event [:re-frame.query/query-success :books/list {} [{:id 1} {:id 2}]]) |
| 1104 | + (let [query (get-in (app-db) [:re-frame.query/queries qid])] |
| 1105 | + (is (= :success (:status query))) |
| 1106 | + (is (= [{:id 1} {:id 2}] (:data query))) |
| 1107 | + (is (nil? (:error query)) |
| 1108 | + "error is cleared after successful recovery")))))) |
| 1109 | + |
| 1110 | +(deftest mutation-error-then-retry-success |
| 1111 | + (testing "A failed mutation followed by re-execution transitions to :success" |
| 1112 | + (rfq/reg-mutation :books/create |
| 1113 | + {:mutation-fn (fn [{:keys [title]}] |
| 1114 | + {:method :post :url "/api/books" :body {:title title}})}) |
| 1115 | + (let [mid (util/query-id :books/create {:title "Dune"})] |
| 1116 | + ;; First attempt fails |
| 1117 | + (process-event [:re-frame.query/execute-mutation :books/create {:title "Dune"}]) |
| 1118 | + (process-event [:re-frame.query/mutation-failure :books/create {:title "Dune"} {:status 500}]) |
| 1119 | + (is (= :error (get-in (app-db) [:re-frame.query/mutations mid :status]))) |
| 1120 | + (is (= {:status 500} (get-in (app-db) [:re-frame.query/mutations mid :error]))) |
| 1121 | + ;; Retry succeeds |
| 1122 | + (process-event [:re-frame.query/execute-mutation :books/create {:title "Dune"}]) |
| 1123 | + (is (= :loading (get-in (app-db) [:re-frame.query/mutations mid :status])) |
| 1124 | + "status resets to :loading") |
| 1125 | + (is (nil? (get-in (app-db) [:re-frame.query/mutations mid :error])) |
| 1126 | + "error cleared on retry") |
| 1127 | + (process-event [:re-frame.query/mutation-success :books/create {:title "Dune"} {:id 1}]) |
| 1128 | + (is (= :success (get-in (app-db) [:re-frame.query/mutations mid :status]))) |
| 1129 | + (is (= {:id 1} (get-in (app-db) [:re-frame.query/mutations mid :data]))) |
| 1130 | + (is (nil? (get-in (app-db) [:re-frame.query/mutations mid :error])))))) |
0 commit comments