diff options
| author | Marius Peter <dev@marius-peter.com> | 2025-12-14 18:57:31 +0100 |
|---|---|---|
| committer | Marius Peter <dev@marius-peter.com> | 2025-12-14 18:57:31 +0100 |
| commit | f1c9fd9dd6fe13fc175dc129599998ee3a711bff (patch) | |
| tree | d52bc850d81cb36c5911e7a97221097a41913be0 | |
| parent | eff10228375e74711848b64ac0ce222957d90bfb (diff) | |
| -rw-r--r-- | models/crop-requirement.rkt | 45 | ||||
| -rw-r--r-- | models/crop-rotation.rkt | 158 | ||||
| -rw-r--r-- | models/crop.rkt | 81 | ||||
| -rw-r--r-- | models/fertilizer-product.rkt | 35 | ||||
| -rw-r--r-- | models/nutrient-measurement.rkt | 35 | ||||
| -rw-r--r-- | models/nutrient-value.rkt | 150 |
6 files changed, 446 insertions, 58 deletions
diff --git a/models/crop-requirement.rkt b/models/crop-requirement.rkt index 2dd8071..dc3f3a0 100644 --- a/models/crop-requirement.rkt +++ b/models/crop-requirement.rkt @@ -189,23 +189,23 @@ "../models/nutrient.rkt" "../models/crop.rkt") - (define requirement-profile "Tomato - Vegetative") + (define requirement-profile "Examplium Plant - Vegetative") (run-tests (test-suite "Crop requirement model" #:before (λ () (connect! #:path 'memory) (migrate-all!) - (create-nutrient! "Nitrogen" "Azote" "N") - (create-nutrient! "Phosphorus" "Phosphore" "P") - (create-nutrient! "Potassium" "Potassium" "K")) + (create-nutrient! "Examplium" "Examplium" "Ex") + (create-nutrient! "Ignorium" "Ignorium" "Ig") + (create-nutrient! "Testium" "Testium" "Ts")) #:after (λ () (disconnect!)) (test-case "Create requirement with profile and values" - (define nitrogen (get-nutrient #:name "Nitrogen")) - (define phosphorus (get-nutrient #:name "Phosphorus")) + (define examplium (get-nutrient #:name "Examplium")) + (define ignorium (get-nutrient #:name "Ignorium")) - (create-crop-requirement! requirement-profile (hash nitrogen 150 phosphorus 50)) + (create-crop-requirement! requirement-profile (hash examplium 150 ignorium 50)) (check-equal? (length (get-crop-requirements)) 1) @@ -214,21 +214,22 @@ (check-equal? (crop-requirement-profile cr) requirement-profile)) (test-case "Create requirement with associated crop" - (define tomato (create-crop! "Tomato")) - (define nitrogen (get-nutrient #:name "Nitrogen")) + (define test-crop (create-crop! (crop #f "testium-plant"))) + (define examplium (get-nutrient #:name "Examplium")) - (define cr (create-crop-requirement! "Tomato - Fruiting" (hash nitrogen 200) tomato)) + (define cr + (create-crop-requirement! "Testium Plant - Fruiting" (hash examplium 200) test-crop)) - (check-equal? (crop-requirement-crop-id cr) (crop-id tomato))) + (check-equal? (crop-requirement-crop-id cr) (crop-id test-crop))) (test-case "Check all requirement values" - (define nitrogen (get-nutrient #:name "Nitrogen")) - (define phosphorus (get-nutrient #:name "Phosphorus")) + (define examplium (get-nutrient #:name "Examplium")) + (define ignorium (get-nutrient #:name "Ignorium")) (define cr (get-crop-requirement #:profile requirement-profile)) - (check-= (get-crop-requirement-value cr nitrogen) 150 0) - (check-= (get-crop-requirement-value cr phosphorus) 50 0) + (check-= (get-crop-requirement-value cr examplium) 150 0) + (check-= (get-crop-requirement-value cr ignorium) 50 0) (define crv (crop-requirement-nutrient-values cr)) @@ -238,8 +239,8 @@ "return value of get-crop-requirement-values ≠ crop-requirement-values struct accessor") (check-equal? (hash-count crv) 2) - (check-= (hash-ref crv nitrogen) 150 0) - (check-= (hash-ref crv phosphorus) 50 0)) + (check-= (hash-ref crv examplium) 150 0) + (check-= (hash-ref crv ignorium) 50 0)) (test-case "Get requirement by id" (define cr (get-crop-requirement #:profile requirement-profile)) @@ -248,19 +249,19 @@ (check-equal? cr cr-by-id)) (test-case "Average crop requirement nutrient values" - (define nitrogen (get-nutrient #:name "Nitrogen")) - (define phosphorus (get-nutrient #:name "Phosphorus")) + (define examplium (get-nutrient #:name "Examplium")) + (define ignorium (get-nutrient #:name "Ignorium")) (define cr1 (get-crop-requirement #:profile requirement-profile)) - (define cr2 (create-crop-requirement! "Lettuce" (hash nitrogen 100 phosphorus 30))) + (define cr2 (create-crop-requirement! "Generic Growth" (hash examplium 100 ignorium 30))) (define mix (hash cr1 60 cr2 40)) (define avg (average-crop-requirement-nutrient-values mix)) ;; 150 * 0.6 + 100 * 0.4 = 90 + 40 = 130 - (check-= (hash-ref avg nitrogen) 130 0.01) + (check-= (hash-ref avg examplium) 130 0.01) ;; 50 * 0.6 + 30 * 0.4 = 30 + 12 = 42 - (check-= (hash-ref avg phosphorus) 42 0.01)) + (check-= (hash-ref avg ignorium) 42 0.01)) (test-case "Delete requirement and cascade to requirement values" (define cr (get-crop-requirement #:profile requirement-profile)) diff --git a/models/crop-rotation.rkt b/models/crop-rotation.rkt index 3b0a69d..fb7775f 100644 --- a/models/crop-rotation.rkt +++ b/models/crop-rotation.rkt @@ -3,6 +3,7 @@ (provide crop-rotation crop-rotation? crop-rotation-id + crop-rotation-requirement (rename-out [crop-rotation-rotation-date crop-rotation-date] [crop-rotation-requirement-proportions crop-rotation-requirements]) (contract-out @@ -20,6 +21,9 @@ (struct crop-rotation (id rotation-date requirement-proportions) #:transparent) +(define (crop-rotation-requirement cr requirement) + (hash-ref (crop-rotation-requirement-proportions cr) requirement #f)) + (define crop-rotation-or-id/c (or/c crop-rotation? db-id?)) (define requirement-proportion-hash/c (hash/c crop-requirement? (between/c 0 100) #:immutable #t)) @@ -112,3 +116,157 @@ (define (delete-crop-rotation! cr-or-id) (query-exec (current-conn) (delete #:from crop_rotations #:where (= id ,(->cr-id cr-or-id))))) + +(module+ test + (require rackunit + rackunit/text-ui + "../db/conn.rkt" + "../db/migrations.rkt" + "../models/nutrient.rkt" + "../models/crop.rkt" + "../models/crop-requirement.rkt") + + (define rotation-date-1 "2025-01-15") + (define rotation-date-2 "2025-02-20") + + (run-tests + (test-suite "Crop rotation model" + #:before (λ () + (connect! #:path 'memory) + (migrate-all!) + (create-nutrient! "Examplium" "Examplium" "Ex") + (create-nutrient! "Ignorium" "Ignorium" "Ig") + (create-nutrient! "Testium" "Testium" "Ts")) + #:after (λ () (disconnect!)) + + (test-case "Create crop rotation with requirement proportions" + (define ex (get-nutrient #:name "Examplium")) + (define ig (get-nutrient #:name "Ignorium")) + + (define req1 (create-crop-requirement! "vegetative" (hash ex 100 ig 50))) + (define req2 (create-crop-requirement! "fruiting" (hash ex 150 ig 75))) + + (define proportions (hash req1 60 req2 40)) + (define rotation (create-crop-rotation! rotation-date-1 proportions)) + + (check-true (crop-rotation? rotation)) + (check-equal? (crop-rotation-rotation-date rotation) rotation-date-1) + (check-equal? (hash-count (crop-rotation-requirement-proportions rotation)) 2) + + ;; Find requirements in the returned hash by profile + (define reqs (crop-rotation-requirement-proportions rotation)) + (define req1-found + (findf (λ (r) (equal? (crop-requirement-profile r) "vegetative")) (hash-keys reqs))) + (define req2-found + (findf (λ (r) (equal? (crop-requirement-profile r) "fruiting")) (hash-keys reqs))) + + (check-equal? (hash-ref reqs req1-found) 60) + (check-equal? (hash-ref reqs req2-found) 40)) + + (test-case "Create duplicate rotation returns existing" + ;; Get all requirements to find the ones we need + (define all-reqs (get-crop-requirements)) + (define req1 (findf (λ (r) (equal? (crop-requirement-profile r) "vegetative")) all-reqs)) + (define req2 (findf (λ (r) (equal? (crop-requirement-profile r) "fruiting")) all-reqs)) + + ;; Try to create with different proportions - should return existing + (define proportions (hash req1 70 req2 30)) + (define rotation2 (create-crop-rotation! rotation-date-1 proportions)) + + (check-equal? (length (get-crop-rotations)) 1) + + ;; The returned rotation should have original proportions (60/40), not new ones (70/30) + (define reqs2 (crop-rotation-requirement-proportions rotation2)) + (define req1-from-rotation + (findf (λ (r) (equal? (crop-requirement-profile r) "vegetative")) (hash-keys reqs2))) + (define req2-from-rotation + (findf (λ (r) (equal? (crop-requirement-profile r) "fruiting")) (hash-keys reqs2))) + + (check-equal? (hash-ref reqs2 req1-from-rotation) 60) + (check-equal? (hash-ref reqs2 req2-from-rotation) 40)) + + (test-case "Get crop rotation by id" + (define rotation1 (get-crop-rotation #:date rotation-date-1)) + (define rotation2 (get-crop-rotation #:id (crop-rotation-id rotation1))) + (check-equal? (crop-rotation-id rotation1) (crop-rotation-id rotation2)) + (check-equal? (crop-rotation-rotation-date rotation1) (crop-rotation-rotation-date rotation2))) + + (test-case "Get crop rotation by date" + (define rotation (get-crop-rotation #:date rotation-date-1)) + (check-true (crop-rotation? rotation)) + (check-equal? (crop-rotation-rotation-date rotation) rotation-date-1)) + + (test-case "Get crop rotation with both id and date" + (define rotation1 (get-crop-rotation #:date rotation-date-1)) + (define rotation2 (get-crop-rotation #:id (crop-rotation-id rotation1) #:date rotation-date-1)) + (check-equal? (crop-rotation-id rotation1) (crop-rotation-id rotation2))) + + (test-case "Get non-existent crop rotation" + (check-false (get-crop-rotation #:date "2099-12-31")) + (check-false (get-crop-rotation #:id 9999))) + + (test-case "Get all crop rotations ordered by date descending" + (define ts (get-nutrient #:name "Testium")) + (define req3 (create-crop-requirement! "maintenance" (hash ts 80))) + (create-crop-rotation! rotation-date-2 (hash req3 100)) + + (define rotations (get-crop-rotations)) + (check-equal? (length rotations) 2) + ;; Most recent first + (check-equal? (crop-rotation-rotation-date (first rotations)) rotation-date-2) + (check-equal? (crop-rotation-rotation-date (second rotations)) rotation-date-1)) + + (test-case "Get latest crop rotation" + (define latest (get-latest-crop-rotation)) + (check-true (crop-rotation? latest)) + (check-equal? (crop-rotation-rotation-date latest) rotation-date-2)) + + (test-case "Get latest returns #f when no rotations exist" + (for ([rotation (get-crop-rotations)]) + (delete-crop-rotation! (crop-rotation-id rotation))) + (check-false (get-latest-crop-rotation))) + + (test-case "Delete crop rotation by struct" + (define ts (get-nutrient #:name "Testium")) + (define req + (findf (λ (r) (equal? (crop-requirement-profile r) "maintenance")) (get-crop-requirements))) + (create-crop-rotation! "2025-03-01" (hash req 100)) + + (define rotation (get-crop-rotation #:date "2025-03-01")) + (delete-crop-rotation! rotation) + (check-false (get-crop-rotation #:id (crop-rotation-id rotation)))) + + (test-case "Delete crop rotation by id" + (define ts (get-nutrient #:name "Testium")) + (define req + (findf (λ (r) (equal? (crop-requirement-profile r) "maintenance")) (get-crop-requirements))) + (create-crop-rotation! "2025-04-01" (hash req 100)) + + (define rotation (get-crop-rotation #:date "2025-04-01")) + (define rotation-id-val (crop-rotation-id rotation)) + (delete-crop-rotation! rotation-id-val) + (check-false (get-crop-rotation #:id rotation-id-val))) + + (test-case "Delete crop rotation cascades to crop_rotation_requirements" + (define ex (get-nutrient #:name "Examplium")) + (define req1 (create-crop-requirement! "test-profile-1" (hash ex 100))) + (define req2 (create-crop-requirement! "test-profile-2" (hash ex 200))) + (create-crop-rotation! "2025-05-01" (hash req1 50 req2 50)) + + (define initial-count (length (get-crop-rotations))) + (define rotation (get-crop-rotation #:date "2025-05-01")) + (delete-crop-rotation! rotation) + (check-equal? (length (get-crop-rotations)) (- initial-count 1))) + + (test-case "Rotation with associated crop requirement" + (define ex (get-nutrient #:name "Examplium")) + (define test-crop (create-crop! (crop #f "test-crop-for-rotation"))) + (define req-with-crop (create-crop-requirement! "crop-specific" (hash ex 120) test-crop)) + (define rotation (create-crop-rotation! "2025-06-01" (hash req-with-crop 100))) + + (check-equal? (crop-requirement-crop-id req-with-crop) (crop-id test-crop)) + (define retrieved (get-crop-rotation #:date "2025-06-01")) + (check-equal? (hash-count (crop-rotation-requirement-proportions retrieved)) 1)) + + (test-case "Error when neither id nor date provided" + (check-exn exn:fail? (λ () (get-crop-rotation))))))) diff --git a/models/crop.rkt b/models/crop.rkt index ba93550..4430543 100644 --- a/models/crop.rkt +++ b/models/crop.rkt @@ -61,3 +61,84 @@ (define (delete-crop! id) (query-exec (current-conn) (delete #:from crops #:where (= id ,id)))) + +(module+ test + (require rackunit + rackunit/text-ui + "../db/conn.rkt" + "../db/migrations.rkt") + + (define test-crop-name "examplium-plant") + + (run-tests (test-suite "Crop model" + #:before (λ () + (connect! #:path 'memory) + (migrate-all!)) + #:after (λ () (disconnect!)) + + (test-case "Create crop" + (check-equal? (length (get-crops)) 0) + (define c1 (create-crop! (crop #f test-crop-name))) + (check-equal? (length (get-crops)) 1) + (check-true (crop? c1)) + (check-equal? (crop-name c1) test-crop-name) + (check-true (db-id? (crop-id c1)))) + + (test-case "Create duplicate crop returns existing" + (define c1 (create-crop! (crop #f test-crop-name))) + (define c2 (create-crop! (crop #f test-crop-name))) + (check-equal? (length (get-crops)) 1) + (check-equal? (crop-id c1) (crop-id c2)) + (check-equal? c1 c2)) + + (test-case "Get crop by id" + (define c1 (get-crop #:name test-crop-name)) + (define c2 (get-crop #:id (crop-id c1))) + (check-equal? c1 c2)) + + (test-case "Get crop by name" + (define c (get-crop #:name test-crop-name)) + (check-true (crop? c)) + (check-equal? (crop-name c) test-crop-name)) + + (test-case "Get crop with both id and name" + (define c1 (get-crop #:name test-crop-name)) + (define c2 (get-crop #:id (crop-id c1) #:name test-crop-name)) + (check-equal? c1 c2)) + + (test-case "Get non-existent crop" + (check-false (get-crop #:name "non-existent-crop")) + (check-false (get-crop #:id 9999))) + + (test-case "Get all crops" + (create-crop! (crop #f "ignorium-plant")) + (create-crop! (crop #f "testium-plant")) + (define crops (get-crops)) + (check-equal? (length crops) 3) + (check-true (andmap crop? crops))) + + (test-case "Update crop name" + (define c1 (get-crop #:name test-crop-name)) + (define updated-crop (crop (crop-id c1) "examplium-updated")) + (update-crop! updated-crop) + (define c2 (get-crop #:id (crop-id c1))) + (check-equal? (crop-name c2) "examplium-updated") + (check-equal? (crop-id c1) (crop-id c2)) + (check-equal? (length (get-crops)) 3)) + + (test-case "Update crop with invalid id raises error" + (define invalid-crop (crop #f "invalid")) + (check-exn exn:fail:contract? (λ () (update-crop! invalid-crop)))) + + (test-case "Delete crop" + (define c (get-crop #:name "ignorium-plant")) + (delete-crop! (crop-id c)) + (check-false (get-crop #:id (crop-id c))) + (check-equal? (length (get-crops)) 2)) + + (test-case "Delete crop by id" + (define c (get-crop #:name "testium-plant")) + (define crop-id-val (crop-id c)) + (delete-crop! crop-id-val) + (check-false (get-crop #:id crop-id-val)) + (check-equal? (length (get-crops)) 1))))) diff --git a/models/fertilizer-product.rkt b/models/fertilizer-product.rkt index 652b3c4..5d81681 100644 --- a/models/fertilizer-product.rkt +++ b/models/fertilizer-product.rkt @@ -189,41 +189,41 @@ "../db/migrations.rkt" "../models/nutrient.rkt") - (define canonical-product-name "MasterBlend 4-20") + (define canonical-product-name "ExampleBlend 4-20") (run-tests (test-suite "Fertilizer product model" #:before (λ () (connect! #:path 'memory) (migrate-all!) - (create-nutrient! "Nitrogen" "Azote" "N") - (create-nutrient! "Phosphorus" "Phosphore" "P") - (create-nutrient! "Potassium" "Potassium" "K")) + (create-nutrient! "Examplium" "Examplium" "Ex") + (create-nutrient! "Ignorium" "Ignorium" "Ig") + (create-nutrient! "Testium" "Testium" "Ts")) #:after (λ () (disconnect!)) (test-case "Create product with name, brand, and values" - (define nitrogen (get-nutrient #:name "Nitrogen")) - (define phosphorus (get-nutrient #:name "Phosphorus")) + (define examplium (get-nutrient #:name "Examplium")) + (define ignorium (get-nutrient #:name "Ignorium")) (create-fertilizer-product! canonical-product-name - "MasterBlend" - (hash nitrogen 40 phosphorus 200)) + "ExampleBrand" + (hash examplium 40 ignorium 200)) (check-equal? (length (get-fertilizer-products)) 1) (define fp (get-fertilizer-product #:canonical-name canonical-product-name)) (check-true (fertilizer-product? fp)) (check-equal? (fertilizer-product-canonical-name fp) canonical-product-name) - (check-equal? (fertilizer-product-brand-name fp) "MasterBlend")) + (check-equal? (fertilizer-product-brand-name fp) "ExampleBrand")) (test-case "Check all product values" - (define nitrogen (get-nutrient #:name "Nitrogen")) - (define phosphorus (get-nutrient #:name "Phosphorus")) + (define examplium (get-nutrient #:name "Examplium")) + (define ignorium (get-nutrient #:name "Ignorium")) (define fp (get-fertilizer-product #:canonical-name canonical-product-name)) - (check-= (get-fertilizer-product-value fp nitrogen) 40 0) - (check-= (get-fertilizer-product-value fp phosphorus) 200 0) + (check-= (get-fertilizer-product-value fp examplium) 40 0) + (check-= (get-fertilizer-product-value fp ignorium) 200 0) (define fpv (fertilizer-product-nutrient-values fp)) @@ -240,13 +240,13 @@ (check-equal? fp fp-by-id)) (test-case "Handle missing nutrient in product" - (define potassium (get-nutrient #:name "Potassium")) + (define testium (get-nutrient #:name "Testium")) (define fp (get-fertilizer-product #:canonical-name canonical-product-name)) - (check-false (get-fertilizer-product-value fp potassium))) + (check-false (get-fertilizer-product-value fp testium))) (test-case "Custom write property formatting" - (define nitrogen (get-nutrient #:name "Nitrogen")) - (define fp (create-fertilizer-product! "Test Fertilizer" "TestBrand" (hash nitrogen 50))) + (define examplium (get-nutrient #:name "Examplium")) + (define fp (create-fertilizer-product! "Test Fertilizer" "TestBrand" (hash examplium 50))) (define output (open-output-string)) (write fp output) @@ -255,7 +255,6 @@ (check-true (string-contains? result "Fertilizer #")) (check-true (string-contains? result "Test Fertilizer")) (check-true (string-contains? result "TestBrand"))) - (test-case "Delete product and cascade to product values" (define fp (get-fertilizer-product #:canonical-name canonical-product-name)) (delete-fertilizer-product! fp) diff --git a/models/nutrient-measurement.rkt b/models/nutrient-measurement.rkt index 3a5238b..7f954b6 100644 --- a/models/nutrient-measurement.rkt +++ b/models/nutrient-measurement.rkt @@ -221,29 +221,28 @@ (test-suite "Nutrient measurement model" #:before (λ () (connect! #:path 'memory) - ;; (connect! #:path "test.sqlite3") (migrate-all!) - (create-nutrient! "Nitrogen" "" "N") - (create-nutrient! "Phosphorus" "" "P") - (create-nutrient! "Potassium" "" "K")) + (create-nutrient! "Examplium" "Examplium" "Ex") + (create-nutrient! "Ignorium" "Ignorium" "Ig") + (create-nutrient! "Testium" "Testium" "Ts")) #:after (λ () (disconnect!)) (test-case "Create measurement with date and values" - (define nitrogen (get-nutrient #:name "Nitrogen")) - (define phosphorus (get-nutrient #:name "Phosphorus")) - (create-nutrient-measurement! measurement-date (hash nitrogen 12.3 phosphorus 4.5)) + (define examplium (get-nutrient #:name "Examplium")) + (define ignorium (get-nutrient #:name "Ignorium")) + (create-nutrient-measurement! measurement-date (hash examplium 12.3 ignorium 4.5)) (check-equal? (length (get-nutrient-measurements)) 1) (define nm (get-nutrient-measurement #:date measurement-date)) (check-true (nutrient-measurement? nm)) (check-equal? (nutrient-measurement-measurement-date nm) measurement-date)) (test-case "Check all measurement values" - (define nitrogen (get-nutrient #:name "Nitrogen")) - (define phosphorus (get-nutrient #:name "Phosphorus")) + (define examplium (get-nutrient #:name "Examplium")) + (define ignorium (get-nutrient #:name "Ignorium")) (define nm (get-nutrient-measurement #:date measurement-date)) - (check-equal? (get-nutrient-measurement-value nm nitrogen) 12.3) - (check-equal? (get-nutrient-measurement-value nm phosphorus) 4.5) + (check-equal? (get-nutrient-measurement-value nm examplium) 12.3) + (check-equal? (get-nutrient-measurement-value nm ignorium) 4.5) (define nmv (nutrient-measurement-nutrient-values nm)) (check-equal? @@ -251,17 +250,17 @@ nmv "return value of get-nutrient-measurement-values ≠ nutrient-measurement-values struct accessor") (check-equal? (hash-count nmv) 2) - (check-equal? (hash-ref nmv nitrogen) 12.3) - (check-equal? (hash-ref nmv phosphorus) 4.5)) + (check-equal? (hash-ref nmv examplium) 12.3) + (check-equal? (hash-ref nmv ignorium) 4.5)) (test-case "Retrieve latest measurement values" - (define nitrogen (get-nutrient #:name "Nitrogen")) - (define phosphorus (get-nutrient #:name "Phosphorus")) + (define examplium (get-nutrient #:name "Examplium")) + (define ignorium (get-nutrient #:name "Ignorium")) (define second-measurement-date "2025-09-02") - (create-nutrient-measurement! second-measurement-date (hash nitrogen 6.7 phosphorus 8.9)) + (create-nutrient-measurement! second-measurement-date (hash examplium 6.7 ignorium 8.9)) - (check-equal? (get-latest-nutrient-measurement-value nitrogen) 6.7) - (check-equal? (get-latest-nutrient-measurement-value phosphorus) 8.9)) + (check-equal? (get-latest-nutrient-measurement-value examplium) 6.7) + (check-equal? (get-latest-nutrient-measurement-value ignorium) 8.9)) (test-case "Delete measurement and cascade to measurement values" (define nm (get-nutrient-measurement #:date measurement-date)) diff --git a/models/nutrient-value.rkt b/models/nutrient-value.rkt index 9653c7f..4514d84 100644 --- a/models/nutrient-value.rkt +++ b/models/nutrient-value.rkt @@ -51,3 +51,153 @@ (for/hash ([r (in-list residuals)]) (match-define (vector n-id n-canonical-name n-french-name n-formula value-ppm) r) (values (nutrient n-id n-canonical-name n-french-name n-formula) value-ppm))) + +(module+ test + (require rackunit + rackunit/text-ui + db + sql + "../db/conn.rkt" + "../db/migrations.rkt" + "../models/nutrient.rkt") + + (run-tests (test-suite "Nutrient value model" + #:before (λ () + (connect! #:path 'memory) + (migrate-all!) + (create-nutrient! "Examplium" "Examplium" "Ex") + (create-nutrient! "Ignorium" "Ignorium" "Ig") + (create-nutrient! "Testium" "Testium" "Ts") + (create-nutrient! "Zeroium" "Zeroium" "Zr")) + #:after (λ () (disconnect!)) + + #;(test-case "Insert nutrient values" + (define ex (get-nutrient #:name "Examplium")) + (define ig (get-nutrient #:name "Ignorium")) + + ;; Create a nutrient_value_set for testing + (define nvs-id + (insert-id (query (current-conn) + (insert #:into nutrient_value_sets + #:set [nutrient_measurement_id ,sql-null] + [crop_requirement_id ,sql-null] + [fertilizer_product_id ,sql-null])))) + + (define nv-hash (hash ex 100.5 ig 50.25)) + (define result (insert-nutrient-values (current-conn) nvs-id nv-hash)) + + (check-true (list? result)) + (check-true (assoc 'insert-id result)) + + ;; Verify values were inserted + (define ex-value + (query-value (current-conn) + (select value_ppm + #:from nutrient_values + #:where (and (= value_set_id ,nvs-id) + (= nutrient_id ,(nutrient-id ex)))))) + (check-= ex-value 100.5 0.001)) + + (test-case "Get sorted nutrient values filters positive and sorts descending" + (define ex (get-nutrient #:name "Examplium")) + (define ig (get-nutrient #:name "Ignorium")) + (define ts (get-nutrient #:name "Testium")) + (define zr (get-nutrient #:name "Zeroium")) + + (define nv-hash (hash ex 100 ig 50 ts 150 zr 0)) + (define sorted (get-sorted-nutrient-values nv-hash)) + + ;; Should exclude zero values + (check-equal? (length sorted) 3) + ;; Should be sorted descending by value + (check-equal? (cdr (first sorted)) 150) ; Testium + (check-equal? (cdr (second sorted)) 100) ; Examplium + (check-equal? (cdr (third sorted)) 50) ; Ignorium + ;; Zeroium should not be in the list + (check-false (member zr (map car sorted)))) + + (test-case "Get sorted nutrient values with all zeros returns empty" + (define ex (get-nutrient #:name "Examplium")) + (define ig (get-nutrient #:name "Ignorium")) + + (define nv-hash (hash ex 0 ig 0)) + (define sorted (get-sorted-nutrient-values nv-hash)) + + (check-equal? (length sorted) 0)) + + (test-case "Get sorted nutrient values with negatives excluded" + (define ex (get-nutrient #:name "Examplium")) + (define ig (get-nutrient #:name "Ignorium")) + + ;; Note: nutrient-value? contract should prevent this, but testing the function + (define nv-hash (hash ex 100 ig -50)) + (define sorted (get-sorted-nutrient-values nv-hash)) + + (check-equal? (length sorted) 1) + (check-equal? (cdr (first sorted)) 100)) + + #;(test-case "Update nutrient values" + (define ex (get-nutrient #:name "Examplium")) + (define ig (get-nutrient #:name "Ignorium")) + + ;; Create initial values + (define nvs-id + (insert-id (query (current-conn) + (insert #:into nutrient_value_sets + #:set [nutrient_measurement_id ,sql-null] + [crop_requirement_id ,sql-null] + [fertilizer_product_id ,sql-null])))) + + (define initial-hash (hash ex 100 ig 50)) + (insert-nutrient-values (current-conn) nvs-id initial-hash) + + ;; Update values + (define updated-hash (hash ex 200 ig 75)) + (update-nutrient-values! (current-conn) nvs-id updated-hash) + + ;; Verify updates + (define ex-value + (query-value (current-conn) + (select value_ppm + #:from nutrient_values + #:where (and (= value_set_id ,nvs-id) + (= nutrient_id ,(nutrient-id ex)))))) + (check-= ex-value 200 0.001) + + (define ig-value + (query-value (current-conn) + (select value_ppm + #:from nutrient_values + #:where (and (= value_set_id ,nvs-id) + (= nutrient_id ,(nutrient-id ig)))))) + (check-= ig-value 75 0.001)) + + (test-case "Residuals to nutrient value hash conversion" + (define ex (get-nutrient #:name "Examplium")) + (define ig (get-nutrient #:name "Ignorium")) + + (define residuals + (list (vector (nutrient-id ex) "Examplium" "Examplium" "Ex" 123.45) + (vector (nutrient-id ig) "Ignorium" "Ignorium" "Ig" 67.89))) + + (define nv-hash (residuals->nutrient-value-hash residuals)) + + (check-equal? (hash-count nv-hash) 2) + (check-= (hash-ref nv-hash ex) 123.45 0.001) + (check-= (hash-ref nv-hash ig) 67.89 0.001)) + + (test-case "Residuals to nutrient value hash with empty list" + (define nv-hash (residuals->nutrient-value-hash '())) + (check-true (hash-empty? nv-hash))) + + (test-case "Residuals to nutrient value hash preserves all fields" + (define residuals (list (vector 1 "Examplium" "Examplium-FR" "Ex" 100.0))) + + (define nv-hash (residuals->nutrient-value-hash residuals)) + (define nutrient-key (car (hash-keys nv-hash))) + + (check-equal? (nutrient-id nutrient-key) 1) + (check-equal? (nutrient-canonical-name nutrient-key) "Examplium") + (check-equal? (nutrient-french-name nutrient-key) "Examplium-FR") + (check-equal? (nutrient-formula nutrient-key) "Ex") + (check-= (hash-ref nv-hash nutrient-key) 100.0 0.001))))) |