From 73c81a00a10766900318d86640249d1b54c6b351 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 4 Apr 2026 16:32:16 -0500 Subject: feat: specific error messages for fetch failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add HTTP status code checking (wttrin--extract-http-status) and pass error descriptions through the callback chain so users see "Location not found (HTTP 404)" or "Network error — check your connection" instead of the generic "Perhaps the location was misspelled?" for every failure. Also fix pre-existing bug where the condition-case error handler in extract-response-body killed an unrelated buffer after unwind-protect already cleaned up. 330 tests (was 307), all passing. --- tests/test-wttrin--extract-http-status.el | 61 ++++++++ tests/test-wttrin--extract-response-body.el | 28 ++++ tests/test-wttrin--fetch-url.el | 16 +- tests/test-wttrin--get-cached-or-fetch.el | 20 +-- tests/test-wttrin--handle-fetch-callback.el | 72 +++++++-- tests/test-wttrin-ansi-color-rendering.el | 2 +- tests/test-wttrin-error-propagation.el | 231 ++++++++++++++++++++++++++++ tests/test-wttrin-fetch-raw-string.el | 2 +- 8 files changed, 399 insertions(+), 33 deletions(-) create mode 100644 tests/test-wttrin--extract-http-status.el create mode 100644 tests/test-wttrin-error-propagation.el (limited to 'tests') diff --git a/tests/test-wttrin--extract-http-status.el b/tests/test-wttrin--extract-http-status.el new file mode 100644 index 0000000..4a714e2 --- /dev/null +++ b/tests/test-wttrin--extract-http-status.el @@ -0,0 +1,61 @@ +;;; test-wttrin--extract-http-status.el --- Tests for wttrin--extract-http-status -*- lexical-binding: t; -*- + +;; Copyright (C) 2025-2026 Craig Jennings + +;;; Commentary: + +;; Unit tests for wttrin--extract-http-status function. +;; Parses the HTTP status code from url-retrieve's response buffer. + +;;; Code: + +(require 'ert) +(require 'wttrin) +(require 'testutil-wttrin) + +;;; Normal Cases + +(ert-deftest test-wttrin--extract-http-status-normal-200 () + "Standard 200 OK response should return 200." + (with-temp-buffer + (insert "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nbody") + (should (= (wttrin--extract-http-status) 200)))) + +(ert-deftest test-wttrin--extract-http-status-normal-404 () + "404 Not Found response should return 404." + (with-temp-buffer + (insert "HTTP/1.1 404 Not Found\r\n\r\nNot found") + (should (= (wttrin--extract-http-status) 404)))) + +(ert-deftest test-wttrin--extract-http-status-normal-500 () + "500 Internal Server Error should return 500." + (with-temp-buffer + (insert "HTTP/1.1 500 Internal Server Error\r\n\r\nerror") + (should (= (wttrin--extract-http-status) 500)))) + +;;; Boundary Cases + +(ert-deftest test-wttrin--extract-http-status-boundary-no-status-line () + "Buffer with no HTTP status line should return nil." + (with-temp-buffer + (insert "just some text with no HTTP headers") + (should-not (wttrin--extract-http-status)))) + +(ert-deftest test-wttrin--extract-http-status-boundary-http2 () + "HTTP/2 responses use a different format but still have a status code." + (with-temp-buffer + (insert "HTTP/2 301 Moved Permanently\r\n\r\n") + (should (= (wttrin--extract-http-status) 301)))) + +(ert-deftest test-wttrin--extract-http-status-boundary-does-not-move-point () + "Parsing the status should not change point, so it doesn't interfere +with subsequent header/body parsing." + (with-temp-buffer + (insert "HTTP/1.1 200 OK\r\n\r\nbody") + (goto-char (point-min)) + (let ((pos-before (point))) + (wttrin--extract-http-status) + (should (= (point) pos-before))))) + +(provide 'test-wttrin--extract-http-status) +;;; test-wttrin--extract-http-status.el ends here diff --git a/tests/test-wttrin--extract-response-body.el b/tests/test-wttrin--extract-response-body.el index 8dcf2d2..8e6686c 100644 --- a/tests/test-wttrin--extract-response-body.el +++ b/tests/test-wttrin--extract-response-body.el @@ -163,5 +163,33 @@ (should-not (buffer-live-p test-buffer)))) (should (string= "data" result)))) +;;; HTTP Status Code Handling +;; Note: wttrin--extract-response-body kills its buffer, so we capture +;; the result via a let binding before the buffer disappears. + +(ert-deftest test-wttrin--extract-response-body-error-404-returns-nil () + "A 404 response should return nil — the location doesn't exist." + (let (result) + (with-current-buffer (generate-new-buffer " *test-404*") + (insert "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\n\r\nPage not found") + (setq result (wttrin--extract-response-body))) + (should-not result))) + +(ert-deftest test-wttrin--extract-response-body-error-500-returns-nil () + "A 500 response should return nil — the server is broken." + (let (result) + (with-current-buffer (generate-new-buffer " *test-500*") + (insert "HTTP/1.1 500 Internal Server Error\r\n\r\nServer error") + (setq result (wttrin--extract-response-body))) + (should-not result))) + +(ert-deftest test-wttrin--extract-response-body-normal-200-still-returns-body () + "A 200 response should still extract and return the body as before." + (let (result) + (with-current-buffer (generate-new-buffer " *test-200*") + (insert "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nWeather data here") + (setq result (wttrin--extract-response-body))) + (should (equal result "Weather data here")))) + (provide 'test-wttrin--extract-response-body) ;;; test-wttrin--extract-response-body.el ends here diff --git a/tests/test-wttrin--fetch-url.el b/tests/test-wttrin--fetch-url.el index 9a2e58b..4bb02d7 100644 --- a/tests/test-wttrin--fetch-url.el +++ b/tests/test-wttrin--fetch-url.el @@ -41,7 +41,7 @@ (wttrin--fetch-url "http://example.com/weather" - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-called t) (setq callback-data data))) @@ -61,7 +61,7 @@ (wttrin--fetch-url "http://example.com/weather" - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-data data))) (should (string-match-p "☀️" callback-data)) @@ -82,7 +82,7 @@ (wttrin--fetch-url "http://example.com/weather" - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-data data))) ;; Headers should not be in the data @@ -104,7 +104,7 @@ (wttrin--fetch-url "http://example.com/weather" - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-data data))) (should (string= "" callback-data))))) @@ -122,7 +122,7 @@ (wttrin--fetch-url "http://example.com/weather" - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-data data))) (should (= 10000 (length callback-data)))))) @@ -141,7 +141,7 @@ (wttrin--fetch-url "http://example.com/weather" - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-data data))) (should (string= "Weather data" callback-data))))) @@ -160,7 +160,7 @@ (wttrin--fetch-url "http://example.com/weather" - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-called t) (setq callback-data data))) @@ -180,7 +180,7 @@ (wttrin--fetch-url "http://example.com/weather" - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-called t))) (should callback-called) diff --git a/tests/test-wttrin--get-cached-or-fetch.el b/tests/test-wttrin--get-cached-or-fetch.el index 75b4e8b..9bc393d 100644 --- a/tests/test-wttrin--get-cached-or-fetch.el +++ b/tests/test-wttrin--get-cached-or-fetch.el @@ -59,7 +59,7 @@ (wttrin--get-cached-or-fetch location - (lambda (data) (setq callback-result data))) + (lambda (data &optional _error-msg) (setq callback-result data))) ;; Should return cached data immediately (should (equal callback-result test-wttrin--get-cached-or-fetch-sample-weather)) @@ -88,7 +88,7 @@ (wttrin--get-cached-or-fetch location - (lambda (data) (setq callback-result data))) + (lambda (data &optional _error-msg) (setq callback-result data))) ;; Should call fetch (should fetch-called) @@ -125,7 +125,7 @@ Proactive refresh keeps data fresh; on-demand reads always use cache." (wttrin--get-cached-or-fetch location - (lambda (data) (setq callback-result data))) + (lambda (data &optional _error-msg) (setq callback-result data))) ;; Should serve old data without fetching (should-not fetch-called) @@ -160,7 +160,7 @@ Proactive refresh keeps data fresh; on-demand reads always use cache." (wttrin--get-cached-or-fetch location - (lambda (data) (setq callback-result data))) + (lambda (data &optional _error-msg) (setq callback-result data))) ;; Should fetch despite fresh cache (should fetch-called) @@ -189,7 +189,7 @@ Proactive refresh keeps data fresh; on-demand reads always use cache." (wttrin--get-cached-or-fetch location - (lambda (data) (setq callback-result data))) + (lambda (data &optional _error-msg) (setq callback-result data))) ;; Should fetch (should fetch-called) @@ -225,14 +225,14 @@ Proactive refresh keeps data fresh; on-demand reads always use cache." (let ((wttrin-unit-system "m")) (wttrin--get-cached-or-fetch location - (lambda (data) (setq callback-result data))) + (lambda (data &optional _error-msg) (setq callback-result data))) (should (equal callback-result metric-data))) ;; Fetch with imperial - should get imperial cache (let ((wttrin-unit-system "u")) (wttrin--get-cached-or-fetch location - (lambda (data) (setq callback-result data))) + (lambda (data &optional _error-msg) (setq callback-result data))) (should (equal callback-result imperial-data))))) (test-wttrin--get-cached-or-fetch-teardown))) @@ -266,7 +266,7 @@ Proactive refresh keeps data fresh; on-demand reads always use cache." (wttrin--get-cached-or-fetch location - (lambda (data) (setq callback-result data))) + (lambda (data &optional _error-msg) (setq callback-result data))) ;; Should fall back to stale cache (should (equal callback-result test-wttrin--get-cached-or-fetch-sample-weather)) @@ -296,7 +296,7 @@ Proactive refresh keeps data fresh; on-demand reads always use cache." (wttrin--get-cached-or-fetch location - (lambda (data) (setq callback-result data))) + (lambda (data &optional _error-msg) (setq callback-result data))) ;; Should return nil (no fallback available) (should (null callback-result)))) @@ -321,7 +321,7 @@ Proactive refresh keeps data fresh; on-demand reads always use cache." (wttrin--get-cached-or-fetch location - (lambda (data) (setq callback-result data))) + (lambda (data &optional _error-msg) (setq callback-result data))) ;; Should attempt to fetch (nil is a valid location input) (should fetch-called) diff --git a/tests/test-wttrin--handle-fetch-callback.el b/tests/test-wttrin--handle-fetch-callback.el index fb78736..d4158ac 100644 --- a/tests/test-wttrin--handle-fetch-callback.el +++ b/tests/test-wttrin--handle-fetch-callback.el @@ -33,7 +33,7 @@ (lambda () "Weather: ☀️ Sunny"))) (wttrin--handle-fetch-callback nil ;; status with no error - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-called t) (setq callback-data data))) @@ -48,7 +48,7 @@ (lambda () ""))) (wttrin--handle-fetch-callback nil - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-called t) (setq callback-data data))) @@ -64,7 +64,7 @@ (lambda () large-data))) (wttrin--handle-fetch-callback nil - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-called t) (setq callback-data data))) @@ -81,7 +81,7 @@ (lambda () "data"))) (wttrin--handle-fetch-callback nil ;; nil status means success - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-called t))) (should callback-called)))) @@ -94,7 +94,7 @@ (lambda () "data"))) (wttrin--handle-fetch-callback '() ;; empty plist, no error key - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-called t) (setq callback-data data))) @@ -108,7 +108,7 @@ (lambda () "data"))) (wttrin--handle-fetch-callback '(:peer "example.com" :redirect nil) ;; status with other keys - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-called t))) (should callback-called)))) @@ -121,7 +121,7 @@ (callback-data 'not-nil)) (wttrin--handle-fetch-callback '(:error (error "Network unreachable")) - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-called t) (setq callback-data data))) @@ -134,7 +134,7 @@ (callback-data 'not-nil)) (wttrin--handle-fetch-callback '(:error (error "HTTP 404")) - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-called t) (setq callback-data data))) @@ -147,7 +147,7 @@ (callback-data 'not-nil)) (wttrin--handle-fetch-callback '(:error (error "Request timed out")) - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-called t) (setq callback-data data))) @@ -164,7 +164,7 @@ (condition-case err (wttrin--handle-fetch-callback nil - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-called t) (error "User callback error"))) (error @@ -183,7 +183,7 @@ (condition-case err (wttrin--handle-fetch-callback nil - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-called t))) (error (setq error-caught t))) @@ -200,7 +200,7 @@ (lambda () nil))) (wttrin--handle-fetch-callback nil - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-called t) (setq callback-data data))) @@ -213,7 +213,7 @@ (callback-data 'not-nil)) (wttrin--handle-fetch-callback '(:error (error "First error") :another-error "Second error") - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-called t) (setq callback-data data))) @@ -221,5 +221,51 @@ ;; Should return nil when :error key is present (should (null callback-data)))) +;;; User-facing error messages + +(ert-deftest test-wttrin--handle-fetch-callback-error-network-shows-message () + "Network errors should show a specific message in the echo area, +not leave the user guessing." + (let ((displayed-message nil)) + (cl-letf (((symbol-function 'wttrin--extract-response-body) + (lambda () nil)) + ((symbol-function 'message) + (lambda (fmt &rest args) + (setq displayed-message (apply #'format fmt args))))) + (wttrin--handle-fetch-callback + '(:error (error "Network unreachable")) + #'ignore) + (should displayed-message) + (should (string-match-p "network" (downcase displayed-message)))))) + +(ert-deftest test-wttrin--handle-fetch-callback-error-http-404-shows-message () + "HTTP 404 should tell the user the location wasn't found." + (let ((displayed-message nil)) + (cl-letf (((symbol-function 'wttrin--extract-response-body) + (lambda () nil)) + ((symbol-function 'wttrin--extract-http-status) + (lambda () 404)) + ((symbol-function 'message) + (lambda (fmt &rest args) + (setq displayed-message (apply #'format fmt args))))) + ;; No :error in status — url-retrieve succeeded but server returned 404 + (wttrin--handle-fetch-callback nil #'ignore) + (should displayed-message) + (should (string-match-p "not found\\|404" (downcase displayed-message)))))) + +(ert-deftest test-wttrin--handle-fetch-callback-error-http-500-shows-message () + "HTTP 500 should tell the user the weather service had an error." + (let ((displayed-message nil)) + (cl-letf (((symbol-function 'wttrin--extract-response-body) + (lambda () nil)) + ((symbol-function 'wttrin--extract-http-status) + (lambda () 500)) + ((symbol-function 'message) + (lambda (fmt &rest args) + (setq displayed-message (apply #'format fmt args))))) + (wttrin--handle-fetch-callback nil #'ignore) + (should displayed-message) + (should (string-match-p "service\\|server\\|500" (downcase displayed-message)))))) + (provide 'test-wttrin--handle-fetch-callback) ;;; test-wttrin--handle-fetch-callback.el ends here diff --git a/tests/test-wttrin-ansi-color-rendering.el b/tests/test-wttrin-ansi-color-rendering.el index e2bf82b..accada9 100644 --- a/tests/test-wttrin-ansi-color-rendering.el +++ b/tests/test-wttrin-ansi-color-rendering.el @@ -316,7 +316,7 @@ This verifies the fetch function returns unfiltered data from wttr.in." (testutil-wttrin-mock-http-response testutil-wttrin-sample-ansi-response (wttrin-fetch-raw-string location - (lambda (data) + (lambda (data &optional _error-msg) (setq callback-data data))) ;; Verify callback received ANSI codes diff --git a/tests/test-wttrin-error-propagation.el b/tests/test-wttrin-error-propagation.el new file mode 100644 index 0000000..56421fd --- /dev/null +++ b/tests/test-wttrin-error-propagation.el @@ -0,0 +1,231 @@ +;;; test-wttrin-error-propagation.el --- Tests for error info through callbacks -*- lexical-binding: t; -*- + +;; Copyright (C) 2025-2026 Craig Jennings + +;;; Commentary: + +;; Tests that error information flows from the fetch layer through callbacks +;; to the display layer, so users see specific error messages instead of +;; "Perhaps the location was misspelled?" for every failure. + +;;; Code: + +(require 'ert) +(require 'wttrin) +(require 'testutil-wttrin) + +;;; Setup and Teardown + +(defun test-wttrin-error-propagation-setup () + "Setup for error propagation tests." + (testutil-wttrin-setup) + (when (get-buffer "*wttr.in*") + (kill-buffer "*wttr.in*"))) + +(defun test-wttrin-error-propagation-teardown () + "Teardown for error propagation tests." + (testutil-wttrin-teardown) + (when (get-buffer "*wttr.in*") + (kill-buffer "*wttr.in*"))) + +;;; -------------------------------------------------------------------------- +;;; handle-fetch-callback passes error info to callback +;;; -------------------------------------------------------------------------- + +(ert-deftest test-wttrin-error-propagation-network-error-reaches-callback () + "Network error description should be passed as second callback argument." + (let ((received-error nil)) + (cl-letf (((symbol-function 'wttrin--extract-response-body) + (lambda () nil)) + ((symbol-function 'message) #'ignore)) + (wttrin--handle-fetch-callback + '(:error (error "Connection refused")) + (lambda (_data &optional error-msg) + (setq received-error error-msg))) + (should received-error) + (should (string-match-p "network" (downcase received-error)))))) + +(ert-deftest test-wttrin-error-propagation-http-error-reaches-callback () + "HTTP error description should be passed as second callback argument." + (let ((received-error nil)) + (cl-letf (((symbol-function 'wttrin--extract-response-body) + (lambda () nil)) + ((symbol-function 'wttrin--extract-http-status) + (lambda () 404)) + ((symbol-function 'message) #'ignore)) + (wttrin--handle-fetch-callback + nil + (lambda (_data &optional error-msg) + (setq received-error error-msg))) + (should received-error) + (should (string-match-p "not found\\|404" (downcase received-error)))))) + +(ert-deftest test-wttrin-error-propagation-success-no-error () + "Successful fetch should pass nil as the error argument." + (let ((received-error 'not-called)) + (cl-letf (((symbol-function 'wttrin--extract-response-body) + (lambda () "weather data")) + ((symbol-function 'wttrin--extract-http-status) + (lambda () 200))) + (wttrin--handle-fetch-callback + nil + (lambda (_data &optional error-msg) + (setq received-error error-msg))) + (should-not received-error)))) + +;;; -------------------------------------------------------------------------- +;;; get-cached-or-fetch forwards error to caller's callback +;;; -------------------------------------------------------------------------- + +(ert-deftest test-wttrin-error-propagation-cache-miss-forwards-error () + "When fetch fails with no cache, the error message should reach the caller." + (test-wttrin-error-propagation-setup) + (unwind-protect + (let ((received-error nil)) + (cl-letf (((symbol-function 'wttrin-fetch-raw-string) + (lambda (_query callback) + (funcall callback nil "Network error — check your connection")))) + (let ((wttrin--force-refresh t)) + (wttrin--get-cached-or-fetch + "Paris" + (lambda (_data &optional error-msg) + (setq received-error error-msg))) + (should received-error) + (should (string-match-p "network" (downcase received-error)))))) + (test-wttrin-error-propagation-teardown))) + +(ert-deftest test-wttrin-error-propagation-stale-cache-no-error () + "When fetch fails but stale cache exists, serve the data without an error." + (test-wttrin-error-propagation-setup) + (unwind-protect + (let ((received-data nil) + (received-error 'not-called)) + (testutil-wttrin-add-to-cache "Paris" "old weather" 9999) + (cl-letf (((symbol-function 'wttrin-fetch-raw-string) + (lambda (_query callback) + (funcall callback nil "Network error"))) + ((symbol-function 'message) #'ignore)) + (let ((wttrin--force-refresh t)) + (wttrin--get-cached-or-fetch + "Paris" + (lambda (data &optional error-msg) + (setq received-data data) + (setq received-error error-msg))) + ;; Should get stale data, not an error + (should received-data) + (should-not received-error)))) + (test-wttrin-error-propagation-teardown))) + +;;; -------------------------------------------------------------------------- +;;; display-weather shows specific error message +;;; -------------------------------------------------------------------------- + +(ert-deftest test-wttrin-error-propagation-display-shows-specific-error () + "When an error message is provided, display-weather should show it +instead of the generic 'Perhaps the location was misspelled?' text." + (test-wttrin-error-propagation-setup) + (unwind-protect + (let ((displayed-message nil)) + (cl-letf (((symbol-function 'message) + (lambda (fmt &rest args) + (setq displayed-message (apply #'format fmt args))))) + (wttrin--display-weather "Paris" nil "Network error — check your connection") + (should displayed-message) + (should (string-match-p "network" (downcase displayed-message))) + ;; Should NOT show the generic message + (should-not (string-match-p "misspelled" (downcase displayed-message))))) + (test-wttrin-error-propagation-teardown))) + +(ert-deftest test-wttrin-error-propagation-display-falls-back-to-generic () + "When no error message is provided, display-weather should show the +generic message for backward compatibility." + (test-wttrin-error-propagation-setup) + (unwind-protect + (let ((displayed-message nil)) + (cl-letf (((symbol-function 'message) + (lambda (fmt &rest args) + (setq displayed-message (apply #'format fmt args))))) + (wttrin--display-weather "Paris" nil) + (should displayed-message) + (should (string-match-p "Cannot retrieve" displayed-message)))) + (test-wttrin-error-propagation-teardown))) + +;;; -------------------------------------------------------------------------- +;;; End-to-end: full query chain +;;; -------------------------------------------------------------------------- + +(ert-deftest test-wttrin-error-propagation-e2e-404-shows-not-found () + "Full chain: user queries a bad location, API returns 404, user sees +'not found' — not the generic 'misspelled' message." + (test-wttrin-error-propagation-setup) + (unwind-protect + (let ((displayed-message nil)) + (cl-letf (((symbol-function 'url-retrieve) + (lambda (_url callback) + (with-current-buffer (generate-new-buffer " *test-404*") + (insert "HTTP/1.1 404 Not Found\r\n\r\nUnknown location") + (funcall callback nil)))) + ((symbol-function 'message) + (lambda (fmt &rest args) + (setq displayed-message (apply #'format fmt args))))) + (wttrin-query "Nonexistent Place") + (should displayed-message) + (should (string-match-p "not found\\|404" (downcase displayed-message))))) + (test-wttrin-error-propagation-teardown))) + +(ert-deftest test-wttrin-error-propagation-e2e-network-error-shows-network () + "Full chain: network is down, user sees 'network error'." + (test-wttrin-error-propagation-setup) + (unwind-protect + (let ((displayed-message nil)) + (cl-letf (((symbol-function 'url-retrieve) + (lambda (_url callback) + (with-temp-buffer + (funcall callback '(:error (error "Connection refused")))))) + ((symbol-function 'message) + (lambda (fmt &rest args) + (setq displayed-message (apply #'format fmt args))))) + (let ((wttrin--force-refresh t)) + (wttrin-query "Paris")) + (should displayed-message) + (should (string-match-p "network" (downcase displayed-message))))) + (test-wttrin-error-propagation-teardown))) + +;;; -------------------------------------------------------------------------- +;;; Passthrough layers +;;; -------------------------------------------------------------------------- + +(ert-deftest test-wttrin-error-propagation-fetch-raw-string-forwards-error () + "wttrin-fetch-raw-string is a passthrough — error-msg from the fetch +layer must not be dropped." + (let ((received-error nil)) + (cl-letf (((symbol-function 'wttrin--fetch-url) + (lambda (_url callback) + (funcall callback nil "test error message")))) + (wttrin-fetch-raw-string + "Paris" + (lambda (_data &optional error-msg) + (setq received-error error-msg))) + ;; fetch-raw-string calls fetch-url which calls handle-fetch-callback + ;; which calls our callback — but we mocked fetch-url to call directly, + ;; so the error-msg should pass through + (should (equal received-error "test error message"))))) + +(ert-deftest test-wttrin-error-propagation-http-3xx-shows-unexpected () + "HTTP 3xx that reaches us (unusual — url-retrieve follows redirects) +should still produce an error message, not silently return nil." + (let ((received-error nil)) + (cl-letf (((symbol-function 'wttrin--extract-response-body) + (lambda () nil)) + ((symbol-function 'wttrin--extract-http-status) + (lambda () 301)) + ((symbol-function 'message) #'ignore)) + (wttrin--handle-fetch-callback + nil + (lambda (_data &optional error-msg) + (setq received-error error-msg))) + (should received-error) + (should (string-match-p "301\\|unexpected" (downcase received-error)))))) + +(provide 'test-wttrin-error-propagation) +;;; test-wttrin-error-propagation.el ends here diff --git a/tests/test-wttrin-fetch-raw-string.el b/tests/test-wttrin-fetch-raw-string.el index 34db7f9..e775025 100644 --- a/tests/test-wttrin-fetch-raw-string.el +++ b/tests/test-wttrin-fetch-raw-string.el @@ -47,7 +47,7 @@ (lambda (_url callback) (funcall callback "weather response")))) (wttrin-fetch-raw-string "Paris" - (lambda (data) (setq received-data data))) + (lambda (data &optional _error-msg) (setq received-data data))) (should (equal received-data "weather response")))) (test-wttrin-fetch-raw-string-teardown))) -- cgit v1.2.3