[Racket] Ferti hydroponic nutrient solver, redux.
Update model unit tests.
Changed files
models/crop-requirement.rkt
@@ -189,23 +189,23 @@
189
189
"../models/nutrient.rkt"
190
190
"../models/crop.rkt")
191
191
192
Removed:
(define requirement-profile "Tomato - Vegetative")
192
Added:
(define requirement-profile "Examplium Plant - Vegetative")
193
193
194
194
(run-tests
195
195
(test-suite "Crop requirement model"
196
196
#:before (λ ()
197
197
(connect! #:path 'memory)
198
198
(migrate-all!)
199
Removed:
(create-nutrient! "Nitrogen" "Azote" "N")
200
Removed:
(create-nutrient! "Phosphorus" "Phosphore" "P")
201
Removed:
(create-nutrient! "Potassium" "Potassium" "K"))
199
Added:
(create-nutrient! "Examplium" "Examplium" "Ex")
200
Added:
(create-nutrient! "Ignorium" "Ignorium" "Ig")
201
Added:
(create-nutrient! "Testium" "Testium" "Ts"))
202
202
#:after (λ () (disconnect!))
203
203
204
204
(test-case "Create requirement with profile and values"
205
Removed:
(define nitrogen (get-nutrient #:name "Nitrogen"))
206
Removed:
(define phosphorus (get-nutrient #:name "Phosphorus"))
205
Added:
(define examplium (get-nutrient #:name "Examplium"))
206
Added:
(define ignorium (get-nutrient #:name "Ignorium"))
207
207
208
Removed:
(create-crop-requirement! requirement-profile (hash nitrogen 150 phosphorus 50))
208
Added:
(create-crop-requirement! requirement-profile (hash examplium 150 ignorium 50))
209
209
210
210
(check-equal? (length (get-crop-requirements)) 1)
211
211
@@ -214,21 +214,22 @@
214
214
(check-equal? (crop-requirement-profile cr) requirement-profile))
215
215
216
216
(test-case "Create requirement with associated crop"
217
Removed:
(define tomato (create-crop! "Tomato"))
218
Removed:
(define nitrogen (get-nutrient #:name "Nitrogen"))
217
Added:
(define test-crop (create-crop! (crop #f "testium-plant")))
218
Added:
(define examplium (get-nutrient #:name "Examplium"))
219
219
220
Removed:
(define cr (create-crop-requirement! "Tomato - Fruiting" (hash nitrogen 200) tomato))
220
Added:
(define cr
221
Added:
(create-crop-requirement! "Testium Plant - Fruiting" (hash examplium 200) test-crop))
221
222
222
Removed:
(check-equal? (crop-requirement-crop-id cr) (crop-id tomato)))
223
Added:
(check-equal? (crop-requirement-crop-id cr) (crop-id test-crop)))
223
224
224
225
(test-case "Check all requirement values"
225
Removed:
(define nitrogen (get-nutrient #:name "Nitrogen"))
226
Removed:
(define phosphorus (get-nutrient #:name "Phosphorus"))
226
Added:
(define examplium (get-nutrient #:name "Examplium"))
227
Added:
(define ignorium (get-nutrient #:name "Ignorium"))
227
228
228
229
(define cr (get-crop-requirement #:profile requirement-profile))
229
230
230
Removed:
(check-= (get-crop-requirement-value cr nitrogen) 150 0)
231
Removed:
(check-= (get-crop-requirement-value cr phosphorus) 50 0)
231
Added:
(check-= (get-crop-requirement-value cr examplium) 150 0)
232
Added:
(check-= (get-crop-requirement-value cr ignorium) 50 0)
232
233
233
234
(define crv (crop-requirement-nutrient-values cr))
234
235
@@ -238,8 +239,8 @@
238
239
"return value of get-crop-requirement-values ≠ crop-requirement-values struct accessor")
239
240
240
241
(check-equal? (hash-count crv) 2)
241
Removed:
(check-= (hash-ref crv nitrogen) 150 0)
242
Removed:
(check-= (hash-ref crv phosphorus) 50 0))
242
Added:
(check-= (hash-ref crv examplium) 150 0)
243
Added:
(check-= (hash-ref crv ignorium) 50 0))
243
244
244
245
(test-case "Get requirement by id"
245
246
(define cr (get-crop-requirement #:profile requirement-profile))
@@ -248,19 +249,19 @@
248
249
(check-equal? cr cr-by-id))
249
250
250
251
(test-case "Average crop requirement nutrient values"
251
Removed:
(define nitrogen (get-nutrient #:name "Nitrogen"))
252
Removed:
(define phosphorus (get-nutrient #:name "Phosphorus"))
252
Added:
(define examplium (get-nutrient #:name "Examplium"))
253
Added:
(define ignorium (get-nutrient #:name "Ignorium"))
253
254
254
255
(define cr1 (get-crop-requirement #:profile requirement-profile))
255
Removed:
(define cr2 (create-crop-requirement! "Lettuce" (hash nitrogen 100 phosphorus 30)))
256
Added:
(define cr2 (create-crop-requirement! "Generic Growth" (hash examplium 100 ignorium 30)))
256
257
257
258
(define mix (hash cr1 60 cr2 40))
258
259
(define avg (average-crop-requirement-nutrient-values mix))
259
260
260
261
;; 150 * 0.6 + 100 * 0.4 = 90 + 40 = 130
261
Removed:
(check-= (hash-ref avg nitrogen) 130 0.01)
262
Added:
(check-= (hash-ref avg examplium) 130 0.01)
262
263
;; 50 * 0.6 + 30 * 0.4 = 30 + 12 = 42
263
Removed:
(check-= (hash-ref avg phosphorus) 42 0.01))
264
Added:
(check-= (hash-ref avg ignorium) 42 0.01))
264
265
265
266
(test-case "Delete requirement and cascade to requirement values"
266
267
(define cr (get-crop-requirement #:profile requirement-profile))
models/crop-rotation.rkt
@@ -3,6 +3,7 @@
3
3
(provide crop-rotation
4
4
crop-rotation?
5
5
crop-rotation-id
6
Added:
crop-rotation-requirement
6
7
(rename-out [crop-rotation-rotation-date crop-rotation-date]
7
8
[crop-rotation-requirement-proportions crop-rotation-requirements])
8
9
(contract-out
@@ -20,6 +21,9 @@
20
21
21
22
(struct crop-rotation (id rotation-date requirement-proportions) #:transparent)
22
23
24
Added:
(define (crop-rotation-requirement cr requirement)
25
Added:
(hash-ref (crop-rotation-requirement-proportions cr) requirement #f))
26
Added:
23
27
(define crop-rotation-or-id/c (or/c crop-rotation? db-id?))
24
28
(define requirement-proportion-hash/c (hash/c crop-requirement? (between/c 0 100) #:immutable #t))
25
29
@@ -112,3 +116,157 @@
112
116
113
117
(define (delete-crop-rotation! cr-or-id)
114
118
(query-exec (current-conn) (delete #:from crop_rotations #:where (= id ,(->cr-id cr-or-id)))))
119
Added:
120
Added:
(module+ test
121
Added:
(require rackunit
122
Added:
rackunit/text-ui
123
Added:
"../db/conn.rkt"
124
Added:
"../db/migrations.rkt"
125
Added:
"../models/nutrient.rkt"
126
Added:
"../models/crop.rkt"
127
Added:
"../models/crop-requirement.rkt")
128
Added:
129
Added:
(define rotation-date-1 "2025-01-15")
130
Added:
(define rotation-date-2 "2025-02-20")
131
Added:
132
Added:
(run-tests
133
Added:
(test-suite "Crop rotation model"
134
Added:
#:before (λ ()
135
Added:
(connect! #:path 'memory)
136
Added:
(migrate-all!)
137
Added:
(create-nutrient! "Examplium" "Examplium" "Ex")
138
Added:
(create-nutrient! "Ignorium" "Ignorium" "Ig")
139
Added:
(create-nutrient! "Testium" "Testium" "Ts"))
140
Added:
#:after (λ () (disconnect!))
141
Added:
142
Added:
(test-case "Create crop rotation with requirement proportions"
143
Added:
(define ex (get-nutrient #:name "Examplium"))
144
Added:
(define ig (get-nutrient #:name "Ignorium"))
145
Added:
146
Added:
(define req1 (create-crop-requirement! "vegetative" (hash ex 100 ig 50)))
147
Added:
(define req2 (create-crop-requirement! "fruiting" (hash ex 150 ig 75)))
148
Added:
149
Added:
(define proportions (hash req1 60 req2 40))
150
Added:
(define rotation (create-crop-rotation! rotation-date-1 proportions))
151
Added:
152
Added:
(check-true (crop-rotation? rotation))
153
Added:
(check-equal? (crop-rotation-rotation-date rotation) rotation-date-1)
154
Added:
(check-equal? (hash-count (crop-rotation-requirement-proportions rotation)) 2)
155
Added:
156
Added:
;; Find requirements in the returned hash by profile
157
Added:
(define reqs (crop-rotation-requirement-proportions rotation))
158
Added:
(define req1-found
159
Added:
(findf (λ (r) (equal? (crop-requirement-profile r) "vegetative")) (hash-keys reqs)))
160
Added:
(define req2-found
161
Added:
(findf (λ (r) (equal? (crop-requirement-profile r) "fruiting")) (hash-keys reqs)))
162
Added:
163
Added:
(check-equal? (hash-ref reqs req1-found) 60)
164
Added:
(check-equal? (hash-ref reqs req2-found) 40))
165
Added:
166
Added:
(test-case "Create duplicate rotation returns existing"
167
Added:
;; Get all requirements to find the ones we need
168
Added:
(define all-reqs (get-crop-requirements))
169
Added:
(define req1 (findf (λ (r) (equal? (crop-requirement-profile r) "vegetative")) all-reqs))
170
Added:
(define req2 (findf (λ (r) (equal? (crop-requirement-profile r) "fruiting")) all-reqs))
171
Added:
172
Added:
;; Try to create with different proportions - should return existing
173
Added:
(define proportions (hash req1 70 req2 30))
174
Added:
(define rotation2 (create-crop-rotation! rotation-date-1 proportions))
175
Added:
176
Added:
(check-equal? (length (get-crop-rotations)) 1)
177
Added:
178
Added:
;; The returned rotation should have original proportions (60/40), not new ones (70/30)
179
Added:
(define reqs2 (crop-rotation-requirement-proportions rotation2))
180
Added:
(define req1-from-rotation
181
Added:
(findf (λ (r) (equal? (crop-requirement-profile r) "vegetative")) (hash-keys reqs2)))
182
Added:
(define req2-from-rotation
183
Added:
(findf (λ (r) (equal? (crop-requirement-profile r) "fruiting")) (hash-keys reqs2)))
184
Added:
185
Added:
(check-equal? (hash-ref reqs2 req1-from-rotation) 60)
186
Added:
(check-equal? (hash-ref reqs2 req2-from-rotation) 40))
187
Added:
188
Added:
(test-case "Get crop rotation by id"
189
Added:
(define rotation1 (get-crop-rotation #:date rotation-date-1))
190
Added:
(define rotation2 (get-crop-rotation #:id (crop-rotation-id rotation1)))
191
Added:
(check-equal? (crop-rotation-id rotation1) (crop-rotation-id rotation2))
192
Added:
(check-equal? (crop-rotation-rotation-date rotation1) (crop-rotation-rotation-date rotation2)))
193
Added:
194
Added:
(test-case "Get crop rotation by date"
195
Added:
(define rotation (get-crop-rotation #:date rotation-date-1))
196
Added:
(check-true (crop-rotation? rotation))
197
Added:
(check-equal? (crop-rotation-rotation-date rotation) rotation-date-1))
198
Added:
199
Added:
(test-case "Get crop rotation with both id and date"
200
Added:
(define rotation1 (get-crop-rotation #:date rotation-date-1))
201
Added:
(define rotation2 (get-crop-rotation #:id (crop-rotation-id rotation1) #:date rotation-date-1))
202
Added:
(check-equal? (crop-rotation-id rotation1) (crop-rotation-id rotation2)))
203
Added:
204
Added:
(test-case "Get non-existent crop rotation"
205
Added:
(check-false (get-crop-rotation #:date "2099-12-31"))
206
Added:
(check-false (get-crop-rotation #:id 9999)))
207
Added:
208
Added:
(test-case "Get all crop rotations ordered by date descending"
209
Added:
(define ts (get-nutrient #:name "Testium"))
210
Added:
(define req3 (create-crop-requirement! "maintenance" (hash ts 80)))
211
Added:
(create-crop-rotation! rotation-date-2 (hash req3 100))
212
Added:
213
Added:
(define rotations (get-crop-rotations))
214
Added:
(check-equal? (length rotations) 2)
215
Added:
;; Most recent first
216
Added:
(check-equal? (crop-rotation-rotation-date (first rotations)) rotation-date-2)
217
Added:
(check-equal? (crop-rotation-rotation-date (second rotations)) rotation-date-1))
218
Added:
219
Added:
(test-case "Get latest crop rotation"
220
Added:
(define latest (get-latest-crop-rotation))
221
Added:
(check-true (crop-rotation? latest))
222
Added:
(check-equal? (crop-rotation-rotation-date latest) rotation-date-2))
223
Added:
224
Added:
(test-case "Get latest returns #f when no rotations exist"
225
Added:
(for ([rotation (get-crop-rotations)])
226
Added:
(delete-crop-rotation! (crop-rotation-id rotation)))
227
Added:
(check-false (get-latest-crop-rotation)))
228
Added:
229
Added:
(test-case "Delete crop rotation by struct"
230
Added:
(define ts (get-nutrient #:name "Testium"))
231
Added:
(define req
232
Added:
(findf (λ (r) (equal? (crop-requirement-profile r) "maintenance")) (get-crop-requirements)))
233
Added:
(create-crop-rotation! "2025-03-01" (hash req 100))
234
Added:
235
Added:
(define rotation (get-crop-rotation #:date "2025-03-01"))
236
Added:
(delete-crop-rotation! rotation)
237
Added:
(check-false (get-crop-rotation #:id (crop-rotation-id rotation))))
238
Added:
239
Added:
(test-case "Delete crop rotation by id"
240
Added:
(define ts (get-nutrient #:name "Testium"))
241
Added:
(define req
242
Added:
(findf (λ (r) (equal? (crop-requirement-profile r) "maintenance")) (get-crop-requirements)))
243
Added:
(create-crop-rotation! "2025-04-01" (hash req 100))
244
Added:
245
Added:
(define rotation (get-crop-rotation #:date "2025-04-01"))
246
Added:
(define rotation-id-val (crop-rotation-id rotation))
247
Added:
(delete-crop-rotation! rotation-id-val)
248
Added:
(check-false (get-crop-rotation #:id rotation-id-val)))
249
Added:
250
Added:
(test-case "Delete crop rotation cascades to crop_rotation_requirements"
251
Added:
(define ex (get-nutrient #:name "Examplium"))
252
Added:
(define req1 (create-crop-requirement! "test-profile-1" (hash ex 100)))
253
Added:
(define req2 (create-crop-requirement! "test-profile-2" (hash ex 200)))
254
Added:
(create-crop-rotation! "2025-05-01" (hash req1 50 req2 50))
255
Added:
256
Added:
(define initial-count (length (get-crop-rotations)))
257
Added:
(define rotation (get-crop-rotation #:date "2025-05-01"))
258
Added:
(delete-crop-rotation! rotation)
259
Added:
(check-equal? (length (get-crop-rotations)) (- initial-count 1)))
260
Added:
261
Added:
(test-case "Rotation with associated crop requirement"
262
Added:
(define ex (get-nutrient #:name "Examplium"))
263
Added:
(define test-crop (create-crop! (crop #f "test-crop-for-rotation")))
264
Added:
(define req-with-crop (create-crop-requirement! "crop-specific" (hash ex 120) test-crop))
265
Added:
(define rotation (create-crop-rotation! "2025-06-01" (hash req-with-crop 100)))
266
Added:
267
Added:
(check-equal? (crop-requirement-crop-id req-with-crop) (crop-id test-crop))
268
Added:
(define retrieved (get-crop-rotation #:date "2025-06-01"))
269
Added:
(check-equal? (hash-count (crop-rotation-requirement-proportions retrieved)) 1))
270
Added:
271
Added:
(test-case "Error when neither id nor date provided"
272
Added:
(check-exn exn:fail? (λ () (get-crop-rotation)))))))
models/crop.rkt
@@ -61,3 +61,84 @@
61
61
62
62
(define (delete-crop! id)
63
63
(query-exec (current-conn) (delete #:from crops #:where (= id ,id))))
64
Added:
65
Added:
(module+ test
66
Added:
(require rackunit
67
Added:
rackunit/text-ui
68
Added:
"../db/conn.rkt"
69
Added:
"../db/migrations.rkt")
70
Added:
71
Added:
(define test-crop-name "examplium-plant")
72
Added:
73
Added:
(run-tests (test-suite "Crop model"
74
Added:
#:before (λ ()
75
Added:
(connect! #:path 'memory)
76
Added:
(migrate-all!))
77
Added:
#:after (λ () (disconnect!))
78
Added:
79
Added:
(test-case "Create crop"
80
Added:
(check-equal? (length (get-crops)) 0)
81
Added:
(define c1 (create-crop! (crop #f test-crop-name)))
82
Added:
(check-equal? (length (get-crops)) 1)
83
Added:
(check-true (crop? c1))
84
Added:
(check-equal? (crop-name c1) test-crop-name)
85
Added:
(check-true (db-id? (crop-id c1))))
86
Added:
87
Added:
(test-case "Create duplicate crop returns existing"
88
Added:
(define c1 (create-crop! (crop #f test-crop-name)))
89
Added:
(define c2 (create-crop! (crop #f test-crop-name)))
90
Added:
(check-equal? (length (get-crops)) 1)
91
Added:
(check-equal? (crop-id c1) (crop-id c2))
92
Added:
(check-equal? c1 c2))
93
Added:
94
Added:
(test-case "Get crop by id"
95
Added:
(define c1 (get-crop #:name test-crop-name))
96
Added:
(define c2 (get-crop #:id (crop-id c1)))
97
Added:
(check-equal? c1 c2))
98
Added:
99
Added:
(test-case "Get crop by name"
100
Added:
(define c (get-crop #:name test-crop-name))
101
Added:
(check-true (crop? c))
102
Added:
(check-equal? (crop-name c) test-crop-name))
103
Added:
104
Added:
(test-case "Get crop with both id and name"
105
Added:
(define c1 (get-crop #:name test-crop-name))
106
Added:
(define c2 (get-crop #:id (crop-id c1) #:name test-crop-name))
107
Added:
(check-equal? c1 c2))
108
Added:
109
Added:
(test-case "Get non-existent crop"
110
Added:
(check-false (get-crop #:name "non-existent-crop"))
111
Added:
(check-false (get-crop #:id 9999)))
112
Added:
113
Added:
(test-case "Get all crops"
114
Added:
(create-crop! (crop #f "ignorium-plant"))
115
Added:
(create-crop! (crop #f "testium-plant"))
116
Added:
(define crops (get-crops))
117
Added:
(check-equal? (length crops) 3)
118
Added:
(check-true (andmap crop? crops)))
119
Added:
120
Added:
(test-case "Update crop name"
121
Added:
(define c1 (get-crop #:name test-crop-name))
122
Added:
(define updated-crop (crop (crop-id c1) "examplium-updated"))
123
Added:
(update-crop! updated-crop)
124
Added:
(define c2 (get-crop #:id (crop-id c1)))
125
Added:
(check-equal? (crop-name c2) "examplium-updated")
126
Added:
(check-equal? (crop-id c1) (crop-id c2))
127
Added:
(check-equal? (length (get-crops)) 3))
128
Added:
129
Added:
(test-case "Update crop with invalid id raises error"
130
Added:
(define invalid-crop (crop #f "invalid"))
131
Added:
(check-exn exn:fail:contract? (λ () (update-crop! invalid-crop))))
132
Added:
133
Added:
(test-case "Delete crop"
134
Added:
(define c (get-crop #:name "ignorium-plant"))
135
Added:
(delete-crop! (crop-id c))
136
Added:
(check-false (get-crop #:id (crop-id c)))
137
Added:
(check-equal? (length (get-crops)) 2))
138
Added:
139
Added:
(test-case "Delete crop by id"
140
Added:
(define c (get-crop #:name "testium-plant"))
141
Added:
(define crop-id-val (crop-id c))
142
Added:
(delete-crop! crop-id-val)
143
Added:
(check-false (get-crop #:id crop-id-val))
144
Added:
(check-equal? (length (get-crops)) 1)))))
models/fertilizer-product.rkt
@@ -189,41 +189,41 @@
189
189
"../db/migrations.rkt"
190
190
"../models/nutrient.rkt")
191
191
192
Removed:
(define canonical-product-name "MasterBlend 4-20")
192
Added:
(define canonical-product-name "ExampleBlend 4-20")
193
193
194
194
(run-tests
195
195
(test-suite "Fertilizer product model"
196
196
#:before (λ ()
197
197
(connect! #:path 'memory)
198
198
(migrate-all!)
199
Removed:
(create-nutrient! "Nitrogen" "Azote" "N")
200
Removed:
(create-nutrient! "Phosphorus" "Phosphore" "P")
201
Removed:
(create-nutrient! "Potassium" "Potassium" "K"))
199
Added:
(create-nutrient! "Examplium" "Examplium" "Ex")
200
Added:
(create-nutrient! "Ignorium" "Ignorium" "Ig")
201
Added:
(create-nutrient! "Testium" "Testium" "Ts"))
202
202
#:after (λ () (disconnect!))
203
203
204
204
(test-case "Create product with name, brand, and values"
205
Removed:
(define nitrogen (get-nutrient #:name "Nitrogen"))
206
Removed:
(define phosphorus (get-nutrient #:name "Phosphorus"))
205
Added:
(define examplium (get-nutrient #:name "Examplium"))
206
Added:
(define ignorium (get-nutrient #:name "Ignorium"))
207
207
208
208
(create-fertilizer-product! canonical-product-name
209
Removed:
"MasterBlend"
210
Removed:
(hash nitrogen 40 phosphorus 200))
209
Added:
"ExampleBrand"
210
Added:
(hash examplium 40 ignorium 200))
211
211
212
212
(check-equal? (length (get-fertilizer-products)) 1)
213
213
214
214
(define fp (get-fertilizer-product #:canonical-name canonical-product-name))
215
215
(check-true (fertilizer-product? fp))
216
216
(check-equal? (fertilizer-product-canonical-name fp) canonical-product-name)
217
Removed:
(check-equal? (fertilizer-product-brand-name fp) "MasterBlend"))
217
Added:
(check-equal? (fertilizer-product-brand-name fp) "ExampleBrand"))
218
218
219
219
(test-case "Check all product values"
220
Removed:
(define nitrogen (get-nutrient #:name "Nitrogen"))
221
Removed:
(define phosphorus (get-nutrient #:name "Phosphorus"))
220
Added:
(define examplium (get-nutrient #:name "Examplium"))
221
Added:
(define ignorium (get-nutrient #:name "Ignorium"))
222
222
223
223
(define fp (get-fertilizer-product #:canonical-name canonical-product-name))
224
224
225
Removed:
(check-= (get-fertilizer-product-value fp nitrogen) 40 0)
226
Removed:
(check-= (get-fertilizer-product-value fp phosphorus) 200 0)
225
Added:
(check-= (get-fertilizer-product-value fp examplium) 40 0)
226
Added:
(check-= (get-fertilizer-product-value fp ignorium) 200 0)
227
227
228
228
(define fpv (fertilizer-product-nutrient-values fp))
229
229
@@ -240,13 +240,13 @@
240
240
(check-equal? fp fp-by-id))
241
241
242
242
(test-case "Handle missing nutrient in product"
243
Removed:
(define potassium (get-nutrient #:name "Potassium"))
243
Added:
(define testium (get-nutrient #:name "Testium"))
244
244
(define fp (get-fertilizer-product #:canonical-name canonical-product-name))
245
Removed:
(check-false (get-fertilizer-product-value fp potassium)))
245
Added:
(check-false (get-fertilizer-product-value fp testium)))
246
246
247
247
(test-case "Custom write property formatting"
248
Removed:
(define nitrogen (get-nutrient #:name "Nitrogen"))
249
Removed:
(define fp (create-fertilizer-product! "Test Fertilizer" "TestBrand" (hash nitrogen 50)))
248
Added:
(define examplium (get-nutrient #:name "Examplium"))
249
Added:
(define fp (create-fertilizer-product! "Test Fertilizer" "TestBrand" (hash examplium 50)))
250
250
251
251
(define output (open-output-string))
252
252
(write fp output)
@@ -255,7 +255,6 @@
255
255
(check-true (string-contains? result "Fertilizer #"))
256
256
(check-true (string-contains? result "Test Fertilizer"))
257
257
(check-true (string-contains? result "TestBrand")))
258
Removed:
259
258
(test-case "Delete product and cascade to product values"
260
259
(define fp (get-fertilizer-product #:canonical-name canonical-product-name))
261
260
(delete-fertilizer-product! fp)
models/nutrient-measurement.rkt
@@ -221,29 +221,28 @@
221
221
(test-suite "Nutrient measurement model"
222
222
#:before (λ ()
223
223
(connect! #:path 'memory)
224
Removed:
;; (connect! #:path "test.sqlite3")
225
224
(migrate-all!)
226
Removed:
(create-nutrient! "Nitrogen" "" "N")
227
Removed:
(create-nutrient! "Phosphorus" "" "P")
228
Removed:
(create-nutrient! "Potassium" "" "K"))
225
Added:
(create-nutrient! "Examplium" "Examplium" "Ex")
226
Added:
(create-nutrient! "Ignorium" "Ignorium" "Ig")
227
Added:
(create-nutrient! "Testium" "Testium" "Ts"))
229
228
#:after (λ () (disconnect!))
230
229
231
230
(test-case "Create measurement with date and values"
232
Removed:
(define nitrogen (get-nutrient #:name "Nitrogen"))
233
Removed:
(define phosphorus (get-nutrient #:name "Phosphorus"))
234
Removed:
(create-nutrient-measurement! measurement-date (hash nitrogen 12.3 phosphorus 4.5))
231
Added:
(define examplium (get-nutrient #:name "Examplium"))
232
Added:
(define ignorium (get-nutrient #:name "Ignorium"))
233
Added:
(create-nutrient-measurement! measurement-date (hash examplium 12.3 ignorium 4.5))
235
234
(check-equal? (length (get-nutrient-measurements)) 1)
236
235
(define nm (get-nutrient-measurement #:date measurement-date))
237
236
(check-true (nutrient-measurement? nm))
238
237
(check-equal? (nutrient-measurement-measurement-date nm) measurement-date))
239
238
240
239
(test-case "Check all measurement values"
241
Removed:
(define nitrogen (get-nutrient #:name "Nitrogen"))
242
Removed:
(define phosphorus (get-nutrient #:name "Phosphorus"))
240
Added:
(define examplium (get-nutrient #:name "Examplium"))
241
Added:
(define ignorium (get-nutrient #:name "Ignorium"))
243
242
244
243
(define nm (get-nutrient-measurement #:date measurement-date))
245
Removed:
(check-equal? (get-nutrient-measurement-value nm nitrogen) 12.3)
246
Removed:
(check-equal? (get-nutrient-measurement-value nm phosphorus) 4.5)
244
Added:
(check-equal? (get-nutrient-measurement-value nm examplium) 12.3)
245
Added:
(check-equal? (get-nutrient-measurement-value nm ignorium) 4.5)
247
246
248
247
(define nmv (nutrient-measurement-nutrient-values nm))
249
248
(check-equal?
@@ -251,17 +250,17 @@
251
250
nmv
252
251
"return value of get-nutrient-measurement-values ≠ nutrient-measurement-values struct accessor")
253
252
(check-equal? (hash-count nmv) 2)
254
Removed:
(check-equal? (hash-ref nmv nitrogen) 12.3)
255
Removed:
(check-equal? (hash-ref nmv phosphorus) 4.5))
253
Added:
(check-equal? (hash-ref nmv examplium) 12.3)
254
Added:
(check-equal? (hash-ref nmv ignorium) 4.5))
256
255
257
256
(test-case "Retrieve latest measurement values"
258
Removed:
(define nitrogen (get-nutrient #:name "Nitrogen"))
259
Removed:
(define phosphorus (get-nutrient #:name "Phosphorus"))
257
Added:
(define examplium (get-nutrient #:name "Examplium"))
258
Added:
(define ignorium (get-nutrient #:name "Ignorium"))
260
259
(define second-measurement-date "2025-09-02")
261
Removed:
(create-nutrient-measurement! second-measurement-date (hash nitrogen 6.7 phosphorus 8.9))
260
Added:
(create-nutrient-measurement! second-measurement-date (hash examplium 6.7 ignorium 8.9))
262
261
263
Removed:
(check-equal? (get-latest-nutrient-measurement-value nitrogen) 6.7)
264
Removed:
(check-equal? (get-latest-nutrient-measurement-value phosphorus) 8.9))
262
Added:
(check-equal? (get-latest-nutrient-measurement-value examplium) 6.7)
263
Added:
(check-equal? (get-latest-nutrient-measurement-value ignorium) 8.9))
265
264
266
265
(test-case "Delete measurement and cascade to measurement values"
267
266
(define nm (get-nutrient-measurement #:date measurement-date))
models/nutrient-value.rkt
@@ -51,3 +51,153 @@
51
51
(for/hash ([r (in-list residuals)])
52
52
(match-define (vector n-id n-canonical-name n-french-name n-formula value-ppm) r)
53
53
(values (nutrient n-id n-canonical-name n-french-name n-formula) value-ppm)))
54
Added:
55
Added:
(module+ test
56
Added:
(require rackunit
57
Added:
rackunit/text-ui
58
Added:
db
59
Added:
sql
60
Added:
"../db/conn.rkt"
61
Added:
"../db/migrations.rkt"
62
Added:
"../models/nutrient.rkt")
63
Added:
64
Added:
(run-tests (test-suite "Nutrient value model"
65
Added:
#:before (λ ()
66
Added:
(connect! #:path 'memory)
67
Added:
(migrate-all!)
68
Added:
(create-nutrient! "Examplium" "Examplium" "Ex")
69
Added:
(create-nutrient! "Ignorium" "Ignorium" "Ig")
70
Added:
(create-nutrient! "Testium" "Testium" "Ts")
71
Added:
(create-nutrient! "Zeroium" "Zeroium" "Zr"))
72
Added:
#:after (λ () (disconnect!))
73
Added:
74
Added:
#;(test-case "Insert nutrient values"
75
Added:
(define ex (get-nutrient #:name "Examplium"))
76
Added:
(define ig (get-nutrient #:name "Ignorium"))
77
Added:
78
Added:
;; Create a nutrient_value_set for testing
79
Added:
(define nvs-id
80
Added:
(insert-id (query (current-conn)
81
Added:
(insert #:into nutrient_value_sets
82
Added:
#:set [nutrient_measurement_id ,sql-null]
83
Added:
[crop_requirement_id ,sql-null]
84
Added:
[fertilizer_product_id ,sql-null]))))
85
Added:
86
Added:
(define nv-hash (hash ex 100.5 ig 50.25))
87
Added:
(define result (insert-nutrient-values (current-conn) nvs-id nv-hash))
88
Added:
89
Added:
(check-true (list? result))
90
Added:
(check-true (assoc 'insert-id result))
91
Added:
92
Added:
;; Verify values were inserted
93
Added:
(define ex-value
94
Added:
(query-value (current-conn)
95
Added:
(select value_ppm
96
Added:
#:from nutrient_values
97
Added:
#:where (and (= value_set_id ,nvs-id)
98
Added:
(= nutrient_id ,(nutrient-id ex))))))
99
Added:
(check-= ex-value 100.5 0.001))
100
Added:
101
Added:
(test-case "Get sorted nutrient values filters positive and sorts descending"
102
Added:
(define ex (get-nutrient #:name "Examplium"))
103
Added:
(define ig (get-nutrient #:name "Ignorium"))
104
Added:
(define ts (get-nutrient #:name "Testium"))
105
Added:
(define zr (get-nutrient #:name "Zeroium"))
106
Added:
107
Added:
(define nv-hash (hash ex 100 ig 50 ts 150 zr 0))
108
Added:
(define sorted (get-sorted-nutrient-values nv-hash))
109
Added:
110
Added:
;; Should exclude zero values
111
Added:
(check-equal? (length sorted) 3)
112
Added:
;; Should be sorted descending by value
113
Added:
(check-equal? (cdr (first sorted)) 150) ; Testium
114
Added:
(check-equal? (cdr (second sorted)) 100) ; Examplium
115
Added:
(check-equal? (cdr (third sorted)) 50) ; Ignorium
116
Added:
;; Zeroium should not be in the list
117
Added:
(check-false (member zr (map car sorted))))
118
Added:
119
Added:
(test-case "Get sorted nutrient values with all zeros returns empty"
120
Added:
(define ex (get-nutrient #:name "Examplium"))
121
Added:
(define ig (get-nutrient #:name "Ignorium"))
122
Added:
123
Added:
(define nv-hash (hash ex 0 ig 0))
124
Added:
(define sorted (get-sorted-nutrient-values nv-hash))
125
Added:
126
Added:
(check-equal? (length sorted) 0))
127
Added:
128
Added:
(test-case "Get sorted nutrient values with negatives excluded"
129
Added:
(define ex (get-nutrient #:name "Examplium"))
130
Added:
(define ig (get-nutrient #:name "Ignorium"))
131
Added:
132
Added:
;; Note: nutrient-value? contract should prevent this, but testing the function
133
Added:
(define nv-hash (hash ex 100 ig -50))
134
Added:
(define sorted (get-sorted-nutrient-values nv-hash))
135
Added:
136
Added:
(check-equal? (length sorted) 1)
137
Added:
(check-equal? (cdr (first sorted)) 100))
138
Added:
139
Added:
#;(test-case "Update nutrient values"
140
Added:
(define ex (get-nutrient #:name "Examplium"))
141
Added:
(define ig (get-nutrient #:name "Ignorium"))
142
Added:
143
Added:
;; Create initial values
144
Added:
(define nvs-id
145
Added:
(insert-id (query (current-conn)
146
Added:
(insert #:into nutrient_value_sets
147
Added:
#:set [nutrient_measurement_id ,sql-null]
148
Added:
[crop_requirement_id ,sql-null]
149
Added:
[fertilizer_product_id ,sql-null]))))
150
Added:
151
Added:
(define initial-hash (hash ex 100 ig 50))
152
Added:
(insert-nutrient-values (current-conn) nvs-id initial-hash)
153
Added:
154
Added:
;; Update values
155
Added:
(define updated-hash (hash ex 200 ig 75))
156
Added:
(update-nutrient-values! (current-conn) nvs-id updated-hash)
157
Added:
158
Added:
;; Verify updates
159
Added:
(define ex-value
160
Added:
(query-value (current-conn)
161
Added:
(select value_ppm
162
Added:
#:from nutrient_values
163
Added:
#:where (and (= value_set_id ,nvs-id)
164
Added:
(= nutrient_id ,(nutrient-id ex))))))
165
Added:
(check-= ex-value 200 0.001)
166
Added:
167
Added:
(define ig-value
168
Added:
(query-value (current-conn)
169
Added:
(select value_ppm
170
Added:
#:from nutrient_values
171
Added:
#:where (and (= value_set_id ,nvs-id)
172
Added:
(= nutrient_id ,(nutrient-id ig))))))
173
Added:
(check-= ig-value 75 0.001))
174
Added:
175
Added:
(test-case "Residuals to nutrient value hash conversion"
176
Added:
(define ex (get-nutrient #:name "Examplium"))
177
Added:
(define ig (get-nutrient #:name "Ignorium"))
178
Added:
179
Added:
(define residuals
180
Added:
(list (vector (nutrient-id ex) "Examplium" "Examplium" "Ex" 123.45)
181
Added:
(vector (nutrient-id ig) "Ignorium" "Ignorium" "Ig" 67.89)))
182
Added:
183
Added:
(define nv-hash (residuals->nutrient-value-hash residuals))
184
Added:
185
Added:
(check-equal? (hash-count nv-hash) 2)
186
Added:
(check-= (hash-ref nv-hash ex) 123.45 0.001)
187
Added:
(check-= (hash-ref nv-hash ig) 67.89 0.001))
188
Added:
189
Added:
(test-case "Residuals to nutrient value hash with empty list"
190
Added:
(define nv-hash (residuals->nutrient-value-hash '()))
191
Added:
(check-true (hash-empty? nv-hash)))
192
Added:
193
Added:
(test-case "Residuals to nutrient value hash preserves all fields"
194
Added:
(define residuals (list (vector 1 "Examplium" "Examplium-FR" "Ex" 100.0)))
195
Added:
196
Added:
(define nv-hash (residuals->nutrient-value-hash residuals))
197
Added:
(define nutrient-key (car (hash-keys nv-hash)))
198
Added:
199
Added:
(check-equal? (nutrient-id nutrient-key) 1)
200
Added:
(check-equal? (nutrient-canonical-name nutrient-key) "Examplium")
201
Added:
(check-equal? (nutrient-french-name nutrient-key) "Examplium-FR")
202
Added:
(check-equal? (nutrient-formula nutrient-key) "Ex")
203
Added:
(check-= (hash-ref nv-hash nutrient-key) 100.0 0.001)))))