aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test-duet-backend.el39
-rw-r--r--tests/test-duet-safety.el8
-rw-r--r--tests/test-duet-smoke.el4
-rw-r--r--tests/test-duet-transfer.el23
4 files changed, 74 insertions, 0 deletions
diff --git a/tests/test-duet-backend.el b/tests/test-duet-backend.el
index f0a999c..629d2eb 100644
--- a/tests/test-duet-backend.el
+++ b/tests/test-duet-backend.el
@@ -188,6 +188,45 @@ value for a duplicated keyword."
(n (duet--normalize-failure backend '(:exit 1 :stderr "API rate exceeded"))))
(should (eq 'rate-limited (plist-get n :class)))))
+(ert-deftest test-duet-normalize-failure-minimal-branches ()
+ "The minimal normalizer maps each generic failure context to its class."
+ (let ((b (test-duet-backend--fake 'x 1)))
+ (should (eq 'missing-executable
+ (plist-get (duet--normalize-failure b '(:executable-missing t)) :class)))
+ (should (eq 'stalled
+ (plist-get (duet--normalize-failure b '(:timeout t)) :class)))
+ (should (eq 'cancelled
+ (plist-get (duet--normalize-failure b '(:signal 9)) :class)))
+ (should (eq 'backend-unknown-failure
+ (plist-get (duet--normalize-failure b '(:stderr "no exit code")) :class)))))
+
+(ert-deftest test-duet-failure-patterns-predicate-match ()
+ "A :match predicate (not a regexp) is called with the whole context."
+ (let* ((norm (duet-define-cli-failure-patterns
+ (list (list :match (lambda (ctx) (eq 99 (plist-get ctx :exit)))
+ :class 'special :cause "x" :next-actions '(retry)))))
+ (n (funcall norm '(:exit 99 :stderr ""))))
+ (should (eq 'special (plist-get n :class)))))
+
+(ert-deftest test-duet-redact-whole-match-without-group ()
+ "A pattern with no capture group redacts the whole match."
+ (should (equal "<redacted>" (duet--redact "secretvalue" '("secret[a-z]+")))))
+
+(ert-deftest test-duet-backend-check-capability-flags-undeclared ()
+ "A capability asserted but absent from `capabilities' is flagged."
+ (let ((b (test-duet-backend--fake
+ 'cap 10 :cleanup :none
+ :normalizer (duet-define-cli-failure-patterns nil))))
+ (should (cl-some (lambda (s) (string-match-p "resume" s))
+ (duet-backend-check-capability b :resume)))))
+
+(ert-deftest test-duet-backend-check-capability-passes-declared ()
+ "A declared capability on an otherwise-publishable backend passes."
+ (let ((b (test-duet-backend--fake
+ 'cap 10 :cleanup :none :capabilities '(:resume t)
+ :normalizer (duet-define-cli-failure-patterns nil))))
+ (should (null (duet-backend-check-capability b :resume)))))
+
;;; Contract checks — tiered
(ert-deftest test-duet-backend-check-minimum-passes-clean-backend ()
diff --git a/tests/test-duet-safety.el b/tests/test-duet-safety.el
index 450a8ea..3f07b6f 100644
--- a/tests/test-duet-safety.el
+++ b/tests/test-duet-safety.el
@@ -101,6 +101,14 @@
(should (null (duet--plan-safety "/a/f" "/b/f"
(list :file-type (lambda (_) 'file))))))
+(ert-deftest test-duet-safety-plan-detects-case-collision-via-caps ()
+ "The composite invokes the injected existing-names and case-fold predicates."
+ (let ((caps (list :file-type (lambda (_) 'file)
+ :existing-names (lambda (_) '("foo.txt"))
+ :case-insensitive (lambda (_) t))))
+ (should (cl-some (lambda (p) (eq 'case-collision (plist-get p :class)))
+ (duet--plan-safety "/a/Foo.txt" "/b/Foo.txt" caps)))))
+
(ert-deftest test-duet-safety-plan-collects-multiple-problems ()
"A pair tripping several checks reports each problem."
(let* ((caps (list :file-type (lambda (_) 'fifo)
diff --git a/tests/test-duet-smoke.el b/tests/test-duet-smoke.el
index a101dd2..4f88a0e 100644
--- a/tests/test-duet-smoke.el
+++ b/tests/test-duet-smoke.el
@@ -32,5 +32,9 @@
(should (featurep 'duet))
(should (commandp 'duet)))
+(ert-deftest test-duet-smoke-entry-not-yet-implemented ()
+ "The entry command errors until the pane layout lands (Phase 4)."
+ (should-error (duet)))
+
(provide 'test-duet-smoke)
;;; test-duet-smoke.el ends here
diff --git a/tests/test-duet-transfer.el b/tests/test-duet-transfer.el
index 84cf678..ebef0f9 100644
--- a/tests/test-duet-transfer.el
+++ b/tests/test-duet-transfer.el
@@ -180,6 +180,29 @@
(should (null (plist-get spec :argv)))
(should (eq 'rsync-remote-to-remote (plist-get spec :exec-mode))))))
+(ert-deftest test-duet-transfer-spec-async-explicit-nil ()
+ "An explicit :async nil in opts is honored rather than defaulting to t."
+ (test-duet-transfer--with-builtins
+ (let ((spec (duet--transfer-spec '("/tmp/a") "/tmp/b" '(:async nil))))
+ (should (null (plist-get spec :async))))))
+
+(ert-deftest test-duet-rsync-normalizer-classifies-known-stderr ()
+ "The rsync backend's normalizer maps known stderr signatures to classes."
+ (test-duet-transfer--with-builtins
+ (let ((rsync (duet-backend-by-name 'rsync)))
+ (should (eq 'permission-denied
+ (plist-get (duet--normalize-failure
+ rsync '(:exit 23 :stderr "rsync: Permission denied (13)"))
+ :class)))
+ (should (eq 'destination-full
+ (plist-get (duet--normalize-failure
+ rsync '(:exit 11 :stderr "No space left on device"))
+ :class)))
+ (should (eq 'rsync-protocol-mismatch
+ (plist-get (duet--normalize-failure
+ rsync '(:exit 12 :stderr "protocol version mismatch"))
+ :class))))))
+
;;; Conflict planning — pure, prompt-free
(ert-deftest test-duet-plan-conflicts-no-collisions-all-copy ()