aboutsummaryrefslogtreecommitdiff
path: root/tests/test-ai-config-gptel-commands.el
blob: b87c4975e272073966557d56ffa8a2907cef25fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
;;; test-ai-config-gptel-commands.el --- Tests for ai-config gptel command wrappers -*- lexical-binding: t; -*-

;;; Commentary:
;; Second pass on ai-config.  The first batch covered the helpers
;; (auth-source, api-key caching, add-file, clear-buffer, context-
;; clear, insert-model-heading).  This file covers the gptel command
;; wrappers and a few small pure helpers:
;;
;;   cj/gptel--refresh-org-prefix
;;   cj/gptel-backend-and-model
;;   cj/gptel-switch-backend
;;   cj/gptel-add-buffer-file
;;   cj/gptel-add-this-buffer
;;   cj/toggle-gptel
;;
;; The gptel/projectile primitives are stubbed throughout.

;;; Code:

(require 'ert)
(require 'cl-lib)

(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
(require 'ai-config)

;; Dynamic vars gptel would normally own.
(defvar gptel-backend nil)
(defvar gptel-model nil)
(defvar gptel-prompt-prefix-alist nil)

;;; cj/gptel--refresh-org-prefix

(ert-deftest test-ai-config-refresh-org-prefix-updates-alist-entry ()
  "Normal: the advice refreshes the org-mode entry in the prefix alist."
  (let ((gptel-prompt-prefix-alist '((org-mode . "stale\n"))))
    (cj/gptel--refresh-org-prefix)
    (let ((entry (alist-get 'org-mode gptel-prompt-prefix-alist)))
      (should (stringp entry))
      ;; Fresh prefix includes the user-login-name + a timestamp bracket.
      (should (string-match-p "\\[" entry)))))

;;; cj/gptel-backend-and-model

(ert-deftest test-ai-config-backend-and-model-formats-with-vector-backend ()
  "Normal: a vector backend's name element comes through formatted."
  (let ((gptel-backend [unused-slot "Claude" other])
        (gptel-model 'claude-opus-4-6))
    (let ((s (cj/gptel-backend-and-model)))
      (should (string-match-p "Claude" s))
      (should (string-match-p "claude-opus-4-6" s)))))

(ert-deftest test-ai-config-backend-and-model-falls-back-to-ai-when-no-backend ()
  "Boundary: with no backend bound, the string starts with the AI fallback."
  (let ((gptel-backend nil)
        (gptel-model nil))
    (should (string-prefix-p "AI:" (cj/gptel-backend-and-model)))))

;;; cj/gptel-switch-backend

(ert-deftest test-ai-config-switch-backend-sets-backend-and-model ()
  "Normal: switch picks a backend + model, then updates the gptel vars."
  (let ((gptel-backend nil)
        (gptel-model nil)
        (msg nil))
    (cl-letf (((symbol-function 'cj/gptel--available-backends)
               (lambda ()
                 '(("Anthropic - Claude" . anthropic-backend))))
              ((symbol-function 'gptel-backend-models)
               (lambda (_b) '(claude-opus claude-sonnet)))
              ((symbol-function 'completing-read)
               (lambda (prompt collection &rest _)
                 ;; First call -> backend choice; second -> model.
                 (cond
                  ((string-match-p "backend" prompt) "Anthropic - Claude")
                  (t "claude-opus"))))
              ((symbol-function 'message)
               (lambda (fmt &rest args) (setq msg (apply #'format fmt args)))))
      (cj/gptel-switch-backend))
    (should (eq gptel-backend 'anthropic-backend))
    (should (equal gptel-model "claude-opus"))
    (should (string-match-p "Anthropic - Claude" msg))))

(ert-deftest test-ai-config-switch-backend-error-invalid-choice ()
  "Error: an unrecognized backend name signals user-error."
  (cl-letf (((symbol-function 'cj/gptel--available-backends)
             (lambda () '(("Anthropic - Claude" . backend-a))))
            ((symbol-function 'completing-read)
             (lambda (&rest _) "Something Else")))
    (should-error (cj/gptel-switch-backend) :type 'user-error)))

;;; cj/gptel-add-buffer-file

(ert-deftest test-ai-config-add-buffer-file-adds-when-buffer-has-file ()
  "Normal: a buffer that visits a file -> the file is added to context."
  (let ((added nil))
    (with-temp-buffer
      (setq buffer-file-name "/tmp/sample.org")
      (cl-letf (((symbol-function 'completing-read)
                 (lambda (&rest _) (buffer-name)))
                ((symbol-function 'cj/gptel--add-file-to-context)
                 (lambda (f) (setq added f) t))
                ((symbol-function 'message) #'ignore))
        (cj/gptel-add-buffer-file))
      (setq buffer-file-name nil))
    (should (equal added "/tmp/sample.org"))))

(ert-deftest test-ai-config-add-buffer-file-messages-when-no-file ()
  "Boundary: a buffer not visiting a file -> message, no add call."
  (let ((added nil)
        (msg nil))
    (with-temp-buffer
      (cl-letf (((symbol-function 'completing-read)
                 (lambda (&rest _) (buffer-name)))
                ((symbol-function 'cj/gptel--add-file-to-context)
                 (lambda (f) (setq added f) t))
                ((symbol-function 'message)
                 (lambda (fmt &rest args)
                   (setq msg (apply #'format fmt args)))))
        (cj/gptel-add-buffer-file)))
    (should-not added)
    (should (string-match-p "not visiting" msg))))

;;; cj/gptel-add-this-buffer

(ert-deftest test-ai-config-add-this-buffer-calls-gptel-add-with-prefix ()
  "Normal: `cj/gptel-add-this-buffer' calls `gptel-add' with the (4) prefix arg."
  (let ((arg nil))
    (cl-letf (((symbol-function 'featurep) (lambda (_) t))
              ((symbol-function 'gptel-add)
               (lambda (a) (setq arg a)))
              ((symbol-function 'message) #'ignore))
      (with-temp-buffer
        (cj/gptel-add-this-buffer)))
    (should (equal arg '(4)))))

;;; cj/toggle-gptel

(ert-deftest test-ai-config-toggle-gptel-closes-when-window-shown ()
  "Normal: with a window already displaying *AI-Assistant*, toggle deletes it."
  (let* ((buf (generate-new-buffer "*AI-Assistant*"))
         (deleted nil))
    (unwind-protect
        (cl-letf (((symbol-function 'get-buffer-window)
                   (lambda (_b) 'fake-window))
                  ((symbol-function 'delete-window)
                   (lambda (w) (setq deleted w))))
          (cj/toggle-gptel))
      (when (buffer-live-p buf) (kill-buffer buf)))
    (should (eq deleted 'fake-window))))

(provide 'test-ai-config-gptel-commands)
;;; test-ai-config-gptel-commands.el ends here