diff options
| author | Craig Jennings <c@cjennings.net> | 2026-04-26 18:37:47 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-04-26 18:37:47 -0500 |
| commit | 439effb54aa8631751a758cb65140800af7e6e63 (patch) | |
| tree | 10a61a77df500a62def67b9ac1f494d370c0af16 /tests | |
| parent | 537b58c431946ba34146743fc8be4fd671aac6d4 (diff) | |
| download | emacs-wttrin-439effb54aa8631751a758cb65140800af7e6e63.tar.gz emacs-wttrin-439effb54aa8631751a758cb65140800af7e6e63.zip | |
test: cover wttrin-geolocation internal helpers
Four pure helpers in wttrin-geolocation.el were exercised only indirectly through the parser tests: --decode-json, --format-city-region, --lookup-provider, and --extract-body. None of them had direct unit coverage. Edge cases like an empty JSON object, a missing-vs-empty city field, an unknown provider symbol, or a missing HTTP body separator weren't locked.
The new file groups all four functions together. Each gets Normal, Boundary, and Error cases per testing.md. Highlights:
- --decode-json: distinguishes nil input, empty string, and malformed JSON, all of which return nil for different reasons.
- --format-city-region: separates "missing key" from "empty string" since the predicate `(and (stringp city) (> (length city) 0) ...)` short-circuits on either.
- --lookup-provider: tests two of the three built-ins plus a `let`-bound synthetic provider, locking the documented extension point on `wttrin-geolocation--providers`.
- --extract-body: real UTF-8 bytes inserted into a temp buffer (mirroring what `url-retrieve` delivers) verify the decoding path. 4xx, 5xx, and missing-separator paths each get their own test.
21 new tests, all green on first run since they characterize existing behavior.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-wttrin-geolocation--internals.el | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/tests/test-wttrin-geolocation--internals.el b/tests/test-wttrin-geolocation--internals.el new file mode 100644 index 0000000..6ad8384 --- /dev/null +++ b/tests/test-wttrin-geolocation--internals.el @@ -0,0 +1,187 @@ +;;; test-wttrin-geolocation--internals.el --- Tests for geolocation internal helpers -*- lexical-binding: t; -*- + +;; Copyright (C) 2026 Craig Jennings + +;;; Commentary: + +;; Unit tests for the four pure helpers used by the geolocation parsers: +;; - wttrin-geolocation--decode-json +;; - wttrin-geolocation--format-city-region +;; - wttrin-geolocation--lookup-provider +;; - wttrin-geolocation--extract-body +;; +;; These functions are exercised indirectly through the parser tests +;; (test-wttrin-geolocation--parse-ipapi.el and friends), but were not +;; covered directly. This file fills the gap so each helper's edge +;; cases are locked in isolation. + +;;; Code: + +(require 'ert) +(require 'wttrin-geolocation) + +;;; -------------------------------------------------------------------------- +;;; wttrin-geolocation--decode-json +;;; -------------------------------------------------------------------------- + +;;; Normal Cases + +(ert-deftest test-wttrin-geolocation--decode-json-normal-valid-object () + "Valid JSON object decodes to an alist with symbol keys." + (let ((result (wttrin-geolocation--decode-json "{\"city\":\"Paris\",\"region\":\"IDF\"}"))) + (should (equal (cdr (assq 'city result)) "Paris")) + (should (equal (cdr (assq 'region result)) "IDF")))) + +;;; Boundary Cases + +(ert-deftest test-wttrin-geolocation--decode-json-boundary-empty-object () + "Empty JSON object decodes to nil (an empty alist)." + (should (equal (wttrin-geolocation--decode-json "{}") nil))) + +;;; Error Cases + +(ert-deftest test-wttrin-geolocation--decode-json-error-nil-input () + "Nil input returns nil with no signal." + (should-not (wttrin-geolocation--decode-json nil))) + +(ert-deftest test-wttrin-geolocation--decode-json-error-empty-string () + "Empty string returns nil with no signal." + (should-not (wttrin-geolocation--decode-json ""))) + +(ert-deftest test-wttrin-geolocation--decode-json-error-malformed-json () + "Malformed JSON returns nil with no signal." + (should-not (wttrin-geolocation--decode-json "not valid json {"))) + +;;; -------------------------------------------------------------------------- +;;; wttrin-geolocation--format-city-region +;;; -------------------------------------------------------------------------- + +;;; Normal Cases + +(ert-deftest test-wttrin-geolocation--format-city-region-normal-both-present () + "City and region both present produces \"City, Region\"." + (let ((data '((city . "Paris") (region . "Île-de-France")))) + (should (equal (wttrin-geolocation--format-city-region data) + "Paris, Île-de-France")))) + +;;; Boundary Cases + +(ert-deftest test-wttrin-geolocation--format-city-region-boundary-empty-city () + "Empty city string returns nil even when region is present." + (let ((data '((city . "") (region . "Île-de-France")))) + (should-not (wttrin-geolocation--format-city-region data)))) + +(ert-deftest test-wttrin-geolocation--format-city-region-boundary-empty-region () + "Empty region string returns nil even when city is present." + (let ((data '((city . "Paris") (region . "")))) + (should-not (wttrin-geolocation--format-city-region data)))) + +;;; Error Cases + +(ert-deftest test-wttrin-geolocation--format-city-region-error-missing-city-key () + "Missing city key returns nil." + (let ((data '((region . "Île-de-France")))) + (should-not (wttrin-geolocation--format-city-region data)))) + +(ert-deftest test-wttrin-geolocation--format-city-region-error-missing-region-key () + "Missing region key returns nil." + (let ((data '((city . "Paris")))) + (should-not (wttrin-geolocation--format-city-region data)))) + +(ert-deftest test-wttrin-geolocation--format-city-region-error-nil-data () + "Nil data returns nil with no signal." + (should-not (wttrin-geolocation--format-city-region nil))) + +;;; -------------------------------------------------------------------------- +;;; wttrin-geolocation--lookup-provider +;;; -------------------------------------------------------------------------- + +;;; Normal Cases + +(ert-deftest test-wttrin-geolocation--lookup-provider-normal-builtin-ipapi () + "Built-in ipapi provider returns its plist with :name, :url, :parser." + (let ((result (wttrin-geolocation--lookup-provider 'ipapi))) + (should (equal (plist-get result :name) "ipapi.co")) + (should (stringp (plist-get result :url))) + (should (eq (plist-get result :parser) 'wttrin-geolocation--parse-ipapi)))) + +(ert-deftest test-wttrin-geolocation--lookup-provider-normal-builtin-ipinfo () + "Built-in ipinfo provider returns its plist." + (let ((result (wttrin-geolocation--lookup-provider 'ipinfo))) + (should (equal (plist-get result :name) "ipinfo.io")) + (should (eq (plist-get result :parser) 'wttrin-geolocation--parse-ipinfo)))) + +;;; Boundary Cases + +(ert-deftest test-wttrin-geolocation--lookup-provider-boundary-custom-registered () + "User-registered provider is reachable via lookup." + (let ((wttrin-geolocation--providers + (cons '(test-provider + :name "Test" + :url "https://example.invalid/" + :parser ignore) + wttrin-geolocation--providers))) + (let ((result (wttrin-geolocation--lookup-provider 'test-provider))) + (should (equal (plist-get result :name) "Test")) + (should (eq (plist-get result :parser) 'ignore))))) + +;;; Error Cases + +(ert-deftest test-wttrin-geolocation--lookup-provider-error-unknown-symbol () + "Unknown provider symbol signals error." + (should-error (wttrin-geolocation--lookup-provider 'definitely-not-registered) + :type 'error)) + +;;; -------------------------------------------------------------------------- +;;; wttrin-geolocation--extract-body +;;; -------------------------------------------------------------------------- + +;;; Normal Cases + +(ert-deftest test-wttrin-geolocation--extract-body-normal-200-with-body () + "200 OK response with CRLF separator returns the body." + (with-temp-buffer + (insert "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n{\"city\":\"Paris\"}") + (should (equal (wttrin-geolocation--extract-body) + "{\"city\":\"Paris\"}")))) + +(ert-deftest test-wttrin-geolocation--extract-body-normal-utf8-body () + "UTF-8 body bytes are decoded correctly." + (with-temp-buffer + (set-buffer-multibyte nil) + (insert "HTTP/1.1 200 OK\r\n\r\n") + (insert (encode-coding-string "{\"city\":\"São Paulo\"}" 'utf-8)) + (set-buffer-multibyte t) + (should (equal (wttrin-geolocation--extract-body) + "{\"city\":\"São Paulo\"}")))) + +;;; Boundary Cases + +(ert-deftest test-wttrin-geolocation--extract-body-boundary-empty-body () + "200 response with empty body returns the empty string." + (with-temp-buffer + (insert "HTTP/1.1 200 OK\r\n\r\n") + (should (equal (wttrin-geolocation--extract-body) "")))) + +;;; Error Cases + +(ert-deftest test-wttrin-geolocation--extract-body-error-404 () + "404 response returns nil." + (with-temp-buffer + (insert "HTTP/1.1 404 Not Found\r\n\r\nNot Found") + (should-not (wttrin-geolocation--extract-body)))) + +(ert-deftest test-wttrin-geolocation--extract-body-error-500 () + "500 response returns nil." + (with-temp-buffer + (insert "HTTP/1.1 500 Internal Server Error\r\n\r\noops") + (should-not (wttrin-geolocation--extract-body)))) + +(ert-deftest test-wttrin-geolocation--extract-body-error-missing-separator () + "Response without a blank-line separator returns nil." + (with-temp-buffer + (insert "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nbody-without-separator") + (should-not (wttrin-geolocation--extract-body)))) + +(provide 'test-wttrin-geolocation--internals) +;;; test-wttrin-geolocation--internals.el ends here |
