;;;; tests/client-tests.lisp -- fiveam test suite for mcias-client (in-package #:mcias-client-tests) ;;;; ----------------------------------------------------------------------- ;;;; Test suite ;;;; ----------------------------------------------------------------------- (fiveam:def-suite mcias-client-suite :description "Tests for the mcias-client library") (fiveam:in-suite mcias-client-suite) ;;;; ----------------------------------------------------------------------- ;;;; Helper macro ;;;; ----------------------------------------------------------------------- (defmacro with-mock-server ((client-var &key admin-token) &body body) "Spin up a fresh mock server, bind CLIENT-VAR, run BODY, then stop." (let ((port-var (gensym "PORT")) (server-url (gensym "URL"))) `(let* ((,port-var (start-mock-server)) (,server-url (format nil "http://localhost:~A" ,port-var)) (,client-var (make-client ,server-url :token ,admin-token))) (unwind-protect (progn ,@body) (stop-mock-server))))) ;;;; ----------------------------------------------------------------------- ;;;; Condition hierarchy tests ;;;; ----------------------------------------------------------------------- (fiveam:test condition-hierarchy "Verify the condition type hierarchy." (fiveam:is (subtypep 'mcias-auth-error 'mcias-error)) (fiveam:is (subtypep 'mcias-forbidden-error 'mcias-error)) (fiveam:is (subtypep 'mcias-not-found-error 'mcias-error)) (fiveam:is (subtypep 'mcias-input-error 'mcias-error)) (fiveam:is (subtypep 'mcias-conflict-error 'mcias-error)) (fiveam:is (subtypep 'mcias-server-error 'mcias-error))) ;;;; ----------------------------------------------------------------------- ;;;; make-client tests ;;;; ----------------------------------------------------------------------- (fiveam:test make-client-basic "make-client stores base-url and token." (let ((c (make-client "http://localhost:9000" :token "tok123"))) (fiveam:is (string= "http://localhost:9000" (client-base-url c))) (fiveam:is (string= "tok123" (client-token c))))) (fiveam:test make-client-strips-trailing-slash "make-client trims trailing slashes from the URL." (let ((c (make-client "http://localhost:9000///"))) (fiveam:is (string= "http://localhost:9000" (client-base-url c))))) (fiveam:test make-client-no-token "make-client with no :token gives NIL token." (let ((c (make-client "http://localhost:9000"))) (fiveam:is (null (client-token c))))) ;;;; ----------------------------------------------------------------------- ;;;; Server info tests ;;;; ----------------------------------------------------------------------- (fiveam:test health-ok "health returns T for a live server." (with-mock-server (c) (fiveam:is (eq t (health c))))) (fiveam:test get-public-key "get-public-key returns a plist with :kty :crv :x." (with-mock-server (c) (let ((jwk (get-public-key c))) (fiveam:is (string= "OKP" (getf jwk :kty))) (fiveam:is (string= "Ed25519" (getf jwk :crv))) (fiveam:is (stringp (getf jwk :x)))))) ;;;; ----------------------------------------------------------------------- ;;;; Authentication tests ;;;; ----------------------------------------------------------------------- (fiveam:test login-success "Successful login returns a token and stores it in the client." (with-mock-server (c) (multiple-value-bind (token expires-at) (login c "admin" "adminpass") (fiveam:is (stringp token)) (fiveam:is (stringp expires-at)) (fiveam:is (string= token (client-token c)))))) (fiveam:test login-bad-password "Wrong password signals mcias-auth-error." (with-mock-server (c) (fiveam:signals mcias-auth-error (login c "admin" "wrongpassword")))) (fiveam:test login-unknown-user "Unknown username signals mcias-auth-error." (with-mock-server (c) (fiveam:signals mcias-auth-error (login c "nosuchuser" "whatever")))) (fiveam:test logout-clears-token "logout revokes the token server-side and sets client-token to NIL." (with-mock-server (c) (login c "admin" "adminpass") (fiveam:is (stringp (client-token c))) (fiveam:is (eq t (logout c))) (fiveam:is (null (client-token c))))) (fiveam:test renew-token "renew-token replaces the stored token." (with-mock-server (c) (login c "admin" "adminpass") (let ((old-token (client-token c))) (multiple-value-bind (new-token expires-at) (renew-token c) (fiveam:is (stringp new-token)) (fiveam:is (stringp expires-at)) (fiveam:is (not (string= old-token new-token))) (fiveam:is (string= new-token (client-token c))))))) ;;;; ----------------------------------------------------------------------- ;;;; Token validation tests ;;;; ----------------------------------------------------------------------- (fiveam:test validate-token-valid "validate-token returns :valid T for a live token." (with-mock-server (c) (multiple-value-bind (token _expires) (login c "admin" "adminpass") (declare (ignore _expires)) (let ((result (validate-token c token))) (fiveam:is (eq t (getf result :valid))) (fiveam:is (stringp (getf result :sub))))))) (fiveam:test validate-token-after-logout "validate-token returns :valid NIL for a revoked token (not an error)." (with-mock-server (c) (login c "admin" "adminpass") (let ((token (client-token c))) (logout c) (let ((result (validate-token c token))) (fiveam:is (null (getf result :valid))))))) (fiveam:test validate-token-garbage "validate-token returns :valid NIL for a garbage token string." (with-mock-server (c) (let ((result (validate-token c "garbage-token-xyz"))) (fiveam:is (null (getf result :valid)))))) ;;;; ----------------------------------------------------------------------- ;;;; Account management tests ;;;; ----------------------------------------------------------------------- (fiveam:test create-account "create-account returns a plist with :id :username :status." (with-mock-server (c) (login c "admin" "adminpass") (let ((acct (create-account c "newuser" "user" :password "pass123"))) (fiveam:is (stringp (getf acct :id))) (fiveam:is (string= "newuser" (getf acct :username))) (fiveam:is (stringp (getf acct :status)))))) (fiveam:test list-accounts "list-accounts returns a list with at least the admin account." (with-mock-server (c) (login c "admin" "adminpass") (let ((accounts (list-accounts c))) (fiveam:is (listp accounts)) (fiveam:is (>= (length accounts) 1))))) ;;;; ----------------------------------------------------------------------- ;;;; End-to-end lifecycle test ;;;; ----------------------------------------------------------------------- (fiveam:test e2e-login-validate-logout "Full lifecycle: login -> validate (valid) -> logout -> validate (invalid)." (with-mock-server (c) (multiple-value-bind (token _) (login c "admin" "adminpass") (declare (ignore _)) ;; Token should be valid right after login (let ((r1 (validate-token c token))) (fiveam:is (eq t (getf r1 :valid)))) ;; Logout revokes the token (logout c) ;; Token should now be invalid (not an error) (let ((r2 (validate-token c token))) (fiveam:is (null (getf r2 :valid))))))) ;;;; ----------------------------------------------------------------------- ;;;; Entry point ;;;; ----------------------------------------------------------------------- (defun run-all-tests () "Run all mcias-client tests. Returns T if all pass." (let ((results (fiveam:run 'mcias-client-suite))) (fiveam:explain! results) (fiveam:results-status results)))