taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

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:
Apackages/taler-util/src/http-client/challenger.test.ts | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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); +});