commit e6b2cfced44902718938762c8f052d8537a3688b
parent eed630db1b502d5433bf5235e234fe488b10dfbf
Author: Florian Dold <dold@taler.net>
Date: Tue, 21 Jul 2026 03:10:49 +0200
util: test challenger 302 success redirects and token 401 handling
Diffstat:
1 file changed, 80 insertions(+), 0 deletions(-)
diff --git a/packages/taler-util/src/http-client/challenger.test.ts b/packages/taler-util/src/http-client/challenger.test.ts
@@ -0,0 +1,80 @@
+/*
+ This file is part of GNU Taler
+ (C) 2024 Taler Systems S.A.
+
+ GNU Taler 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, or (at your option) any later version.
+
+ GNU Taler 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
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { test } from "node:test";
+import assert from "node:assert";
+import {
+ HeadersImpl,
+ HttpRequestLibrary,
+ HttpRequestOptions,
+ HttpResponse,
+} from "../http-common.js";
+import { HttpStatusCode } from "../http-status-codes.js";
+import { isOperationFail, isOperationOk } from "../operation.js";
+import { ChallengerHttpClient } from "./challenger.js";
+
+function fixedLib(
+ status: number,
+ headers: Record<string, string> = {},
+): { lib: HttpRequestLibrary; lastOpt: () => HttpRequestOptions | undefined } {
+ let seen: HttpRequestOptions | undefined;
+ const lib: HttpRequestLibrary = {
+ async fetch(url: string, opt?: HttpRequestOptions): Promise<HttpResponse> {
+ seen = opt;
+ const h = new HeadersImpl();
+ h.set("content-type", "application/json");
+ for (const [k, v] of Object.entries(headers)) h.set(k, v);
+ // A minimal well-formed Taler error body, so failure branches that parse
+ // the response body do not choke.
+ const body = { code: 1 };
+ return {
+ requestUrl: url,
+ requestMethod: opt?.method ?? "GET",
+ status,
+ headers: h,
+ async json() {
+ return body;
+ },
+ async text() {
+ return JSON.stringify(body);
+ },
+ async bytes() {
+ return new Uint8Array();
+ },
+ };
+ },
+ };
+ return { lib, lastOpt: () => seen };
+}
+
+const REDIRECT = "https://client.example.com/cb?code=XYZ";
+
+test("solve returns the completed redirect on a 302", async (t) => {
+ const { lib } = fixedLib(HttpStatusCode.Found, { location: REDIRECT });
+ const client = new ChallengerHttpClient("https://challenger.example.com/", lib);
+ const res = await client.solve("nonce", { pin: "1234" });
+ assert.ok(isOperationOk(res), "302 must be a success");
+ assert.strictEqual((res.body as any).type, "completed");
+ assert.strictEqual((res.body as any).redirect_url, REDIRECT);
+});
+
+test("token reports 401 for a bad client secret", async (t) => {
+ const { lib } = fixedLib(HttpStatusCode.Unauthorized);
+ const client = new ChallengerHttpClient("https://challenger.example.com/", lib);
+ const res = await client.token("clientId", "https://cb/", "secret" as any, "code");
+ assert.ok(isOperationFail(res), "401 must be a known failure");
+ assert.strictEqual(res.case, HttpStatusCode.Unauthorized);
+});