nekomata / tests /socket-tests.lisp
SNAPKITTYWEST's picture
push from SNAPKITTYWEST/nekomata
3b70664 verified
Raw
History Blame Contribute Delete
15.1 kB
(in-package #:cl-user)
;;; Nekomata socket layer tests
;;; Deterministic unit tests (no live Docker required)
;;; Integration tests clearly marked — require /var/run/docker.sock
(defpackage #:nekod.test.socket
(:use #:cl))
(in-package #:nekod.test.socket)
(defvar *tests-run* 0)
(defvar *tests-passed* 0)
(defvar *tests-failed* 0)
(defvar *test-results* nil)
(defmacro deftest (name &body body)
`(progn
(incf *tests-run*)
(handler-case
(progn ,@body
(incf *tests-passed*)
(push (cons ',name :pass) *test-results*)
(format t " PASS: ~a~%" ',name))
(error (e)
(incf *tests-failed*)
(push (cons ',name e) *test-results*)
(format t " FAIL: ~a — ~a~%" ',name e)))))
(defmacro assert-true (expr)
`(unless ,expr
(error "Assertion failed: ~s" ',expr)))
(defmacro assert-equal (expected actual)
`(let ((e ,expected) (a ,actual))
(unless (equal e a)
(error "Expected ~s got ~s" e a))))
(defmacro assert-signals (condition-type &body body)
`(let ((signaled nil))
(handler-case (progn ,@body)
(,condition-type (c) (declare (ignore c)) (setf signaled t)))
(unless signaled
(error "Expected ~a to be signaled" ',condition-type))))
;;; =========================================================
;;; UNIT TESTS — No live socket required
;;; =========================================================
(defun run-unit-tests ()
(format t "~%=== Nekomata Socket Layer — Unit Tests ===~%~%")
(setf *tests-run* 0 *tests-passed* 0 *tests-failed* 0 *test-results* nil)
;; Phase 1: Socket transport conditions
(format t "--- Socket Transport ---~%")
(deftest socket-connect-failure-signals-condition
(assert-signals nekod:socket-connect-error
(nekod.docker:connect-socket "/nonexistent/path/docker.sock")))
(deftest disconnect-is-idempotent
(let ((sock (nekod.docker::make-docker-socket
:path "/test" :descriptor nil :stream nil :connected nil)))
(nekod.docker:disconnect-socket sock)
(nekod.docker:disconnect-socket sock)
(nekod.docker:disconnect-socket sock)
(assert-true (not (nekod.docker:docker-socket-connected sock)))))
;; Phase 2: HTTP parsing
(format t "~%--- HTTP Protocol ---~%")
(deftest parse-status-line-200
(multiple-value-bind (ver status reason)
(nekod.docker::parse-status-line "HTTP/1.1 200 OK")
(assert-equal "HTTP/1.1" ver)
(assert-equal 200 status)
(assert-equal "OK" reason)))
(deftest parse-status-line-204
(multiple-value-bind (ver status reason)
(nekod.docker::parse-status-line "HTTP/1.1 204 No Content")
(assert-equal 204 status)
(assert-equal "No Content" reason)))
(deftest parse-http-headers-basic
(let ((headers (nekod.docker::parse-http-headers
(format nil "Content-Type: application/json~%Content-Length: 42"))))
(assert-equal "application/json" (nekod.docker::header-value headers "content-type"))
(assert-equal "42" (nekod.docker::header-value headers "content-length"))))
(deftest header-value-missing-returns-nil
(let ((headers (nekod.docker::parse-http-headers "Content-Type: text/plain")))
(assert-true (null (nekod.docker::header-value headers "x-missing")))))
(deftest decode-chunk-size-hex
(assert-equal 255 (nekod.docker::decode-chunk-size "ff"))
(assert-equal 16 (nekod.docker::decode-chunk-size "10"))
(assert-equal 0 (nekod.docker::decode-chunk-size "0")))
(deftest build-http-request-format
(let ((req (nekod.docker:build-http-request "GET" "/_ping")))
(assert-true (search "GET /_ping HTTP/1.1" req))
(assert-true (search "Host: localhost" req))
(assert-true (search "Connection: close" req))))
;; Phase 3: Regex algebra and DFA
(format t "~%--- Regex DFA ---~%")
(deftest regex-literal-compiles-to-dfa
(let* ((re (nekod.regex:re-literal "start"))
(dfa (nekod.regex::compile-regex-to-dfa re)))
(assert-true (nekod.regex::dfa-run dfa "start"))
(assert-true (not (nekod.regex::dfa-run dfa "stop")))
(assert-true (not (nekod.regex::dfa-run dfa "star")))))
(deftest regex-union-compiles
(let* ((re (nekod.regex:re-union
(nekod.regex:re-literal "die")
(nekod.regex:re-literal "kill")))
(dfa (nekod.regex::compile-regex-to-dfa re)))
(assert-true (nekod.regex::dfa-run dfa "die"))
(assert-true (nekod.regex::dfa-run dfa "kill"))
(assert-true (not (nekod.regex::dfa-run dfa "stop")))))
(deftest regex-concat-compiles
(let* ((re (nekod.regex:re-concat
(nekod.regex:re-literal "foo")
(nekod.regex:re-literal "bar")))
(dfa (nekod.regex::compile-regex-to-dfa re)))
(assert-true (nekod.regex::dfa-run dfa "foobar"))
(assert-true (not (nekod.regex::dfa-run dfa "foo")))
(assert-true (not (nekod.regex::dfa-run dfa "bar")))))
(deftest dfa-equals-reference-for-lifecycle
(nekod.regex:init-default-filters)
(dolist (action '("start" "stop" "die" "kill" "pause" "unpause"))
(assert-true (nekod.regex::filter-event-dfa action :lifecycle)))
(assert-true (not (nekod.regex::filter-event-dfa "unknown" :lifecycle))))
(deftest dfa-equals-reference-for-critical
(nekod.regex:init-default-filters)
(dolist (action '("die" "kill" "oom"))
(assert-true (nekod.regex::filter-event-dfa action :critical)))
(assert-true (not (nekod.regex::filter-event-dfa "start" :critical))))
(deftest dfa-equals-reference-for-network
(nekod.regex:init-default-filters)
(dolist (action '("connect" "disconnect" "create" "destroy"))
(assert-true (nekod.regex::filter-event-dfa action :network)))
(assert-true (not (nekod.regex::filter-event-dfa "die" :network))))
;; Phase 4: Event classification
(format t "~%--- Event Classification ---~%")
(deftest lifecycle-filter-classifies
(nekod.regex:init-default-filters)
(let ((classes (nekod.regex:classify-event
"{\"Type\":\"container\",\"Action\":\"start\",\"ID\":\"abc123\"}")))
(assert-true (member :lifecycle classes))))
(deftest network-filter-classifies
(nekod.regex:init-default-filters)
(let ((classes (nekod.regex:classify-event
"{\"Type\":\"network\",\"Action\":\"connect\",\"ID\":\"net1\"}")))
(assert-true (member :network classes))))
(deftest critical-oom-classifies
(nekod.regex:init-default-filters)
(let ((classes (nekod.regex:classify-event
"{\"Type\":\"container\",\"Action\":\"oom\",\"ID\":\"dead1\"}")))
(assert-true (member :critical classes))))
;; Phase 5: Feature vector derivation
(format t "~%--- Feature Vectors ---~%")
(deftest routing-features-deterministic
(nekod.regex:init-default-filters)
(let* ((event "{\"Type\":\"container\",\"Action\":\"die\",\"ID\":\"x\"}")
(v1 (nekod.regex:event-to-feature-vector event))
(v2 (nekod.regex:event-to-feature-vector event)))
(assert-true (equalp v1 v2))))
(deftest critical-event-sets-urgency
(nekod.regex:init-default-filters)
(let ((features (nekod.regex:event-to-feature-vector
"{\"Type\":\"container\",\"Action\":\"die\",\"ID\":\"x\"}")))
(assert-equal 1.0d0 (aref features 7))))
(deftest auth-event-sets-is-auth
(nekod.regex:init-default-filters)
(let ((features (nekod.regex:event-to-feature-vector
"{\"Type\":\"container\",\"Action\":\"auth_check\",\"ID\":\"x\"}")))
(assert-equal 1.0d0 (aref features 1))))
;; Phase 6: MoE routing
(format t "~%--- Sparse MoE Routing ---~%")
(deftest top-k-activates-exactly-two
(let* ((router (nekod.moe:make-router 2))
(h (make-array 8 :element-type 'double-float :initial-element 0.5d0))
(selected (nekod.moe:top-k-experts router h)))
(assert-equal 2 (length selected))))
(deftest top-k-weights-sum-to-one
(let* ((router (nekod.moe:make-router 2))
(h (make-array 8 :element-type 'double-float :initial-element 0.5d0))
(routing (nekod.moe:route router h))
(total (reduce #'+ routing :key #'cdr)))
(assert-true (< (abs (- total 1.0d0)) 0.001d0))))
(deftest unauthorized-expert-zero-activation
(nekod.socket:init-default-capabilities)
(nekod.socket:revoke-capability "container.read")
(let* ((router (nekod.moe:make-router 2))
(h (make-array 8 :element-type 'double-float :initial-element 1.0d0))
(routing (nekod.moe:route router h)))
(assert-true (= 2 (length routing)))))
;; Phase 7: Backpressure
(format t "~%--- Backpressure ---~%")
(deftest oversized-event-rejected
(nekod.socket:reset-backpressure)
(let ((big-event (make-string (* 512 1024) :initial-element #\x)))
(assert-signals nekod:event-too-large
(nekod.socket:enqueue-event big-event))))
(deftest normal-event-accepted
(nekod.socket:reset-backpressure)
(let ((event "{\"Type\":\"container\",\"Action\":\"start\"}"))
(assert-equal :accepted (nekod.socket:enqueue-event event))))
;; Phase 8: Security policy
(format t "~%--- Security Policy ---~%")
(deftest unauthorized-path-denied
(nekod.socket:init-default-capabilities)
(assert-signals nekod:policy-denied
(nekod.socket:authorize-operation "DELETE" "/volumes/prune")))
(deftest authorized-path-allowed
(nekod.socket:init-default-capabilities)
(let ((result (nekod.socket:authorize-operation "GET" "/containers/json")))
(assert-true (stringp result))))
(deftest untrusted-socket-path-rejected
(assert-signals nekod:policy-denied
(nekod.socket:validate-socket-path "/tmp/evil.sock")))
(deftest trusted-socket-path-allowed
(nekod.socket:validate-socket-path "/var/run/docker.sock"))
;; Phase 9: Receipts
(format t "~%--- Receipts ---~%")
(deftest receipt-creation
(let ((r (nekod.socket:make-operation-receipt
:method "GET" :path "/_ping"
:response-status 200
:response-body "OK"
:socket-path "/var/run/docker.sock"
:policy "ALLOW")))
(assert-true (nekod.socket::receipt-p r))
(assert-equal "GET" (nekod.socket::receipt-request-method r))
(assert-equal 200 (nekod.socket::receipt-response-status r))))
(deftest receipt-no-secrets
(let* ((r (nekod.socket:make-operation-receipt
:method "POST" :path "/auth"
:request-body "{\"password\":\"secret123\"}"
:response-status 200
:response-body "OK"))
(alist (nekod.socket:receipt-to-alist r)))
(assert-true (not (search "secret123" (format nil "~a" alist))))))
;; Summary
(format t "~%=== Results: ~a/~a passed, ~a failed ===~%"
*tests-passed* *tests-run* *tests-failed*)
(values *tests-passed* *tests-run* *tests-failed*))
;;; =========================================================
;;; INTEGRATION TESTS — Require live Docker socket
;;; =========================================================
(defun docker-available-p ()
"Check if Docker socket is accessible."
(probe-file "/var/run/docker.sock"))
(defun run-integration-tests ()
(format t "~%=== Nekomata Socket Layer — Integration Tests ===~%")
(unless (docker-available-p)
(format t "SKIP: /var/run/docker.sock not available~%")
(return-from run-integration-tests nil))
(setf *tests-run* 0 *tests-passed* 0 *tests-failed* 0 *test-results* nil)
(format t "--- Live Docker Socket ---~%")
(deftest integration-connect-and-ping
(let ((sock (nekod.docker:connect-socket "/var/run/docker.sock")))
(unwind-protect
(progn
(assert-true (nekod.docker:socket-open-p sock))
(multiple-value-bind (status body)
(nekod.docker:docker-get sock "/_ping")
(assert-equal 200 status)
(assert-true (search "OK" body))))
(nekod.docker:disconnect-socket sock))))
(deftest integration-version
(let ((sock (nekod.docker:connect-socket "/var/run/docker.sock")))
(unwind-protect
(multiple-value-bind (status body)
(nekod.docker:docker-get sock "/version")
(assert-equal 200 status)
(assert-true (search "ApiVersion" body)))
(nekod.docker:disconnect-socket sock))))
(deftest integration-list-containers
(let ((sock (nekod.docker:connect-socket "/var/run/docker.sock")))
(unwind-protect
(multiple-value-bind (status body)
(nekod.docker:docker-get sock "/containers/json?all=true")
(assert-equal 200 status)
(assert-true (stringp body)))
(nekod.docker:disconnect-socket sock))))
(deftest integration-full-pipeline
(nekod.regex:init-default-filters)
(nekod.socket:init-default-capabilities)
(nekod.socket:reset-backpressure)
(let* ((router (nekod.moe:make-router 2))
(event "{\"Type\":\"container\",\"Action\":\"start\",\"ID\":\"test123\"}")
(features (nekod.regex:event-to-feature-vector event))
(routing (nekod.moe:route router features))
(receipt (nekod.socket:make-operation-receipt
:method "EVENT" :path "/events"
:request-body event
:response-status 200
:response-body event
:event-classes '(:lifecycle)
:feature-vector features
:experts (mapcar #'car routing)
:weights (mapcar #'cdr routing)
:policy "ALLOW")))
(assert-true (= 2 (length routing)))
(assert-true (nekod.socket::receipt-p receipt))
(format t " Routed to: ~{~a~^, ~}~%"
(mapcar #'car routing))))
(format t "~%=== Integration Results: ~a/~a passed, ~a failed ===~%"
*tests-passed* *tests-run* *tests-failed*)
(values *tests-passed* *tests-run* *tests-failed*))
;;; =========================================================
;;; Run all
;;; =========================================================
(defun run-all-tests ()
(multiple-value-bind (p1 r1 f1) (run-unit-tests)
(multiple-value-bind (p2 r2 f2) (run-integration-tests)
(let ((total-pass (+ p1 (or p2 0)))
(total-run (+ r1 (or r2 0)))
(total-fail (+ f1 (or f2 0))))
(format t "~%=== TOTAL: ~a/~a passed, ~a failed ===~%"
total-pass total-run total-fail)
(values total-pass total-run total-fail)))))