aboutsummaryrefslogtreecommitdiff
path: root/tests/test-pearl-query.el
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test-pearl-query.el')
-rw-r--r--tests/test-pearl-query.el151
1 files changed, 151 insertions, 0 deletions
diff --git a/tests/test-pearl-query.el b/tests/test-pearl-query.el
new file mode 100644
index 0000000..87e48b9
--- /dev/null
+++ b/tests/test-pearl-query.el
@@ -0,0 +1,151 @@
+;;; test-pearl-query.el --- Tests for the general issue query -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2026 Craig Jennings
+
+;; Author: Craig Jennings <c@cjennings.net>
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Tests for `pearl--query-issues-async' and the `--page-issues' pager,
+;; with `--graphql-request-async' stubbed. Cover the query/variable
+;; construction, pagination across pages, the page-cap truncation, and the
+;; full set of result statuses (ok / empty / graphql-failed / request-failed).
+
+;;; Code:
+
+(require 'test-bootstrap (expand-file-name "test-bootstrap.el"))
+(require 'cl-lib)
+
+(defun test-lq--page (nodes has-next &optional cursor)
+ "Build a raw issues-page response with NODES, HAS-NEXT, and CURSOR."
+ `((data (issues (nodes . ,(vconcat nodes))
+ (pageInfo (hasNextPage . ,(if has-next t :json-false))
+ (endCursor . ,(or cursor "c")))))))
+
+;;; construction
+
+(ert-deftest test-pearl-query-issues-construction ()
+ "The query targets issues(filter:) and passes the filter + default orderBy."
+ (let (captured result)
+ (cl-letf (((symbol-function 'pearl--graphql-request-async)
+ (lambda (query variables success-fn _error-fn)
+ (setq captured (list query variables))
+ (funcall success-fn (test-lq--page '(((id . "i1"))) nil)))))
+ (pearl--query-issues-async '(("assignee" ("isMe" ("eq" . t))))
+ (lambda (r) (setq result r)))
+ (should (string-match-p "issues(filter:" (car captured)))
+ (should (equal '(("assignee" ("isMe" ("eq" . t)))) (cdr (assoc "filter" (cadr captured)))))
+ (should (string= "updatedAt" (cdr (assoc "orderBy" (cadr captured)))))
+ (should (= 100 (cdr (assoc "first" (cadr captured))))))))
+
+(ert-deftest test-pearl-query-issues-no-filter-omits-variable ()
+ "With a nil filter, the filter variable is omitted (no filter applied)."
+ (let (vars)
+ (cl-letf (((symbol-function 'pearl--graphql-request-async)
+ (lambda (_q variables success-fn _e)
+ (setq vars variables)
+ (funcall success-fn (test-lq--page '() nil)))))
+ (pearl--query-issues-async nil #'ignore)
+ (should-not (assoc "filter" vars)))))
+
+(ert-deftest test-pearl-query-issues-order-by-override ()
+ "An explicit ORDER-BY overrides the updatedAt default."
+ (let (vars)
+ (cl-letf (((symbol-function 'pearl--graphql-request-async)
+ (lambda (_q variables success-fn _e)
+ (setq vars variables)
+ (funcall success-fn (test-lq--page '() nil)))))
+ (pearl--query-issues-async nil #'ignore 'createdAt)
+ (should (string= "createdAt" (cdr (assoc "orderBy" vars)))))))
+
+;;; result statuses
+
+(ert-deftest test-pearl-query-issues-single-page-ok ()
+ "A single page with issues yields an ok result carrying the raw nodes."
+ (let (result)
+ (cl-letf (((symbol-function 'pearl--graphql-request-async)
+ (lambda (_q _v success-fn _e)
+ (funcall success-fn (test-lq--page '(((id . "i1")) ((id . "i2"))) nil)))))
+ (pearl--query-issues-async nil (lambda (r) (setq result r)))
+ (should (eq 'ok (pearl--query-result-status result)))
+ (should (= 2 (length (pearl--query-result-issues result))))
+ (should-not (pearl--query-result-truncated-p result)))))
+
+(ert-deftest test-pearl-query-issues-empty ()
+ "A page with no nodes yields an empty result, not a failure."
+ (let (result)
+ (cl-letf (((symbol-function 'pearl--graphql-request-async)
+ (lambda (_q _v success-fn _e)
+ (funcall success-fn (test-lq--page '() nil)))))
+ (pearl--query-issues-async nil (lambda (r) (setq result r)))
+ (should (eq 'empty (pearl--query-result-status result))))))
+
+(ert-deftest test-pearl-query-issues-graphql-error ()
+ "A GraphQL error response surfaces as a graphql-failed result."
+ (let (result)
+ (cl-letf (((symbol-function 'pearl--graphql-request-async)
+ (lambda (_q _v success-fn _e)
+ (funcall success-fn '((errors . (((message . "bad filter")))))))) )
+ (pearl--query-issues-async nil (lambda (r) (setq result r)))
+ (should (eq 'graphql-failed (pearl--query-result-status result)))
+ (should (string= "bad filter" (pearl--query-result-message result))))))
+
+(ert-deftest test-pearl-query-issues-transport-error ()
+ "A transport failure (error callback) surfaces as request-failed."
+ (let (result)
+ (cl-letf (((symbol-function 'pearl--graphql-request-async)
+ (lambda (_q _v _success-fn error-fn)
+ (funcall error-fn "boom" nil nil))))
+ (pearl--query-issues-async nil (lambda (r) (setq result r)))
+ (should (eq 'request-failed (pearl--query-result-status result))))))
+
+;;; pagination
+
+(ert-deftest test-pearl-query-issues-paginates ()
+ "Multiple pages accumulate; the cursor drives the next fetch."
+ (let ((result nil) (calls 0))
+ (cl-letf (((symbol-function 'pearl--graphql-request-async)
+ (lambda (_q variables success-fn _e)
+ (setq calls (1+ calls))
+ (if (assoc "after" variables)
+ (funcall success-fn (test-lq--page '(((id . "i2"))) nil))
+ (funcall success-fn (test-lq--page '(((id . "i1"))) t "cur"))))))
+ (pearl--query-issues-async nil (lambda (r) (setq result r)))
+ (should (= 2 calls))
+ (should (eq 'ok (pearl--query-result-status result)))
+ (should (= 2 (length (pearl--query-result-issues result)))))))
+
+(ert-deftest test-pearl-query-issues-cap-truncates ()
+ "Hitting the page cap stops paging and marks the result truncated."
+ (let ((result nil) (calls 0)
+ (pearl-max-issue-pages 3))
+ (cl-letf (((symbol-function 'pearl--graphql-request-async)
+ (lambda (_q _v success-fn _e)
+ (setq calls (1+ calls))
+ (funcall success-fn (test-lq--page '(((id . "x"))) t "cur")))))
+ (pearl--query-issues-async nil (lambda (r) (setq result r)))
+ (should (pearl--query-result-truncated-p result))
+ (should (= 3 calls))
+ (should (= 3 (length (pearl--query-result-issues result)))))))
+
+;;; the bulk query fetches comments so the list can render them
+
+(ert-deftest test-pearl-issues-query-requests-comments ()
+ "The bulk issues query selects comments, so a populated list shows them."
+ (should (string-match-p "comments[[:space:]]*{[[:space:]]*nodes" pearl--issues-query)))
+
+(provide 'test-pearl-query)
+;;; test-pearl-query.el ends here