;; lib/flow/tests/integration.sx — Phase 7: end-to-end flows composing every phase. (define flow-int-pass 0) (define flow-int-fail 0) (define flow-int-fails (list)) (define flow-int-test (fn (name actual expected) (if (= actual expected) (set! flow-int-pass (+ flow-int-pass 1)) (begin (set! flow-int-fail (+ flow-int-fail 1)) (append! flow-int-fails {:name name :expected expected :actual actual}))))) (define flow-i (fn (src) (flow-run src))) ;; The order-processing flow, defined once per program via this prelude string: ;; validate amount (attempt: fail if <= 0) ;; -> suspend for payment confirmation (resume value = confirmed amount) ;; -> branch: confirmed>0 ? record on the ledger peer : declined failure (define order-prelude "(flow-peer-register! (quote ledger) (list (list (quote record) (lambda (amt) (list (quote recorded) amt)))))\n (defflow order\n (attempt\n (lambda (amt) (if (> amt 0) amt (fail (quote invalid-amount))))\n (lambda (amt) (suspend (quote await-payment)))\n (branch (lambda (amt) (> amt 0))\n (remote-node (quote ledger) (quote record))\n (flow-const (fail (quote declined))))))") ;; ── happy path through every phase ────────────────────────────── (flow-int-test "order: validate -> suspend -> resume -> branch -> federate" (flow-i (str order-prelude "(define id (car (cdr (flow/start order 100)))) (flow/resume id 250)")) (list "recorded" 250)) (flow-int-test "order: starting suspends awaiting payment" (flow-i (str order-prelude "(define s (flow/start order 100)) (list (car s) (car (cdr (cdr s))))")) (list "flow-suspended" "await-payment")) (flow-int-test "order: invalid amount fails up front and never suspends" (flow-i (str order-prelude "(define r (flow/start order -5)) (list (failed? r) (fail-reason r))")) (list true "invalid-amount")) (flow-int-test "order: a declined payment yields a failure value" (flow-i (str order-prelude "(define id (car (cdr (flow/start order 100)))) (failed? (flow/resume id 0))")) true) ;; ── crash recovery mid-flow ───────────────────────────────────── (flow-int-test "order: survives a simulated crash between suspend and resume" (flow-i (str order-prelude "(define id (car (cdr (flow/start order 100)))) (define saved (flow-store-export)) (set! flow-store (list)) (flow-store-import! saved) (flow/resume id 250)")) (list "recorded" 250)) ;; ── handoff to a peer mid-flow ────────────────────────────────── (flow-int-test "order: hands off to a peer that resumes and completes" (flow-i (str order-prelude "(define id (car (cdr (flow/start order 100)))) (flow-replicate-to (quote nodeB)) (set! flow-store (list)) (flow-restore-from (quote nodeB)) (flow/resume id 250)")) (list "recorded" 250)) ;; ── introspection during the flow's life ──────────────────────── (flow-int-test "order: pending shows what the flow awaits, then result after resume" (flow-i (str order-prelude "(define id (car (cdr (flow/start order 100)))) (define p (flow/pending)) (flow/resume id 250) (list p (flow/status id) (flow/result id))")) (list (list (list 1 "await-payment")) "done" (list "recorded" 250))) ;; ── onboarding: two human steps + cancellation ────────────────── (define onboard-prelude "(defflow onboard\n (sequence\n (lambda (user) (+ user 1))\n (lambda (x) (suspend (quote confirm-email)))\n (lambda (x) (suspend (quote complete-profile)))\n (lambda (x) (list (quote onboarded) x))))") (flow-int-test "onboard: two suspends resume in order to completion" (flow-i (str onboard-prelude "(define id (car (cdr (flow/start onboard 0)))) (flow/resume id 7) (flow/resume id 9)")) (list "onboarded" 9)) (flow-int-test "onboard: the second pending tag appears after the first resume" (flow-i (str onboard-prelude "(define id (car (cdr (flow/start onboard 0)))) (flow/resume id 7) (car (cdr (car (flow/pending))))")) "complete-profile") (flow-int-test "onboard: cancelling abandons the flow" (flow-i (str onboard-prelude "(define id (car (cdr (flow/start onboard 0)))) (flow/cancel id) (list (flow/status id) (car (flow/resume id 7)))")) (list "cancelled" "flow-error")) (define flow-int-tests-run! (fn () {:total (+ flow-int-pass flow-int-fail) :passed flow-int-pass :failed flow-int-fail :fails flow-int-fails}))