aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/merchant-backoffice-ui/package.json11
-rw-r--r--packages/merchant-backoffice-ui/src/InstanceRoutes.tsx4
-rw-r--r--packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/context/api.ts (renamed from packages/merchant-backoffice-ui/tests/__mocks__/browserMocks.ts)45
-rw-r--r--packages/merchant-backoffice-ui/src/context/backend.test.ts131
-rw-r--r--packages/merchant-backoffice-ui/src/context/backend.ts3
-rw-r--r--packages/merchant-backoffice-ui/src/context/fetch.ts54
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/async.ts4
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/backend.ts461
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/index.ts15
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/instance.test.ts660
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/instance.ts113
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/order.test.ts579
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/order.ts97
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/product.test.ts326
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/product.ts69
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/reserve.test.ts431
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/reserves.ts90
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/templates.ts92
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/testing.tsx120
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/transfer.test.ts277
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/transfer.ts80
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/urls.ts291
-rw-r--r--packages/merchant-backoffice-ui/src/manifest.json21
-rw-r--r--packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx9
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/reserves/details/index.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/reserves/list/index.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/utils/regex.test.ts (renamed from packages/merchant-backoffice-ui/tests/functions/regex.test.ts)9
-rw-r--r--packages/merchant-backoffice-ui/src/utils/request.ts282
-rw-r--r--packages/merchant-backoffice-ui/src/utils/switchableAxios.ts73
-rw-r--r--packages/merchant-backoffice-ui/tests/__mocks__/fileMocks.ts24
-rw-r--r--packages/merchant-backoffice-ui/tests/__mocks__/fileTransformer.js31
-rw-r--r--packages/merchant-backoffice-ui/tests/__mocks__/setupTests.ts28
-rw-r--r--packages/merchant-backoffice-ui/tests/axiosMock.ts445
-rw-r--r--packages/merchant-backoffice-ui/tests/context/backend.test.tsx172
-rw-r--r--packages/merchant-backoffice-ui/tests/declarations.d.ts28
-rw-r--r--packages/merchant-backoffice-ui/tests/header.test.tsx63
-rw-r--r--packages/merchant-backoffice-ui/tests/hooks/async.test.ts158
-rw-r--r--packages/merchant-backoffice-ui/tests/hooks/listener.test.ts62
-rw-r--r--packages/merchant-backoffice-ui/tests/hooks/notification.test.ts51
-rw-r--r--packages/merchant-backoffice-ui/tests/hooks/swr/index.tsx46
-rw-r--r--packages/merchant-backoffice-ui/tests/hooks/swr/instance.test.ts636
-rw-r--r--packages/merchant-backoffice-ui/tests/hooks/swr/order.test.ts567
-rw-r--r--packages/merchant-backoffice-ui/tests/hooks/swr/product.test.ts338
-rw-r--r--packages/merchant-backoffice-ui/tests/hooks/swr/reserve.test.ts470
-rw-r--r--packages/merchant-backoffice-ui/tests/hooks/swr/transfer.test.ts268
58 files changed, 3519 insertions, 4245 deletions
diff --git a/packages/merchant-backoffice-ui/package.json b/packages/merchant-backoffice-ui/package.json
index beacd42f6..cffe73e3f 100644
--- a/packages/merchant-backoffice-ui/package.json
+++ b/packages/merchant-backoffice-ui/package.json
@@ -35,7 +35,6 @@
"dependencies": {
"@gnu-taler/taler-util": "workspace:*",
"@gnu-taler/web-util": "workspace:*",
- "axios": "^0.21.1",
"date-fns": "2.29.3",
"history": "4.10.1",
"jed": "1.1.1",
@@ -48,10 +47,8 @@
"devDependencies": {
"@creativebulma/bulma-tooltip": "^1.2.0",
"@gnu-taler/pogen": "^0.0.5",
- "@testing-library/preact": "^2.0.1",
- "@testing-library/preact-hooks": "^1.1.0",
+ "@types/chai": "^4.3.0",
"@types/history": "^4.7.8",
- "@types/jest": "^26.0.23",
"@types/mocha": "^8.2.3",
"@types/node": "^18.11.17",
"@typescript-eslint/eslint-plugin": "^4.22.0",
@@ -64,6 +61,7 @@
"bulma-switch-control": "^1.1.1",
"bulma-timeline": "^3.0.4",
"bulma-upload-control": "^1.2.0",
+ "chai": "^4.3.6",
"dotenv": "^8.2.0",
"eslint": "^7.25.0",
"eslint-config-preact": "^1.1.4",
@@ -72,13 +70,12 @@
"html-webpack-inline-source-plugin": "0.0.10",
"html-webpack-skip-assets-plugin": "^1.0.1",
"inline-chunk-html-plugin": "^1.1.1",
- "jest": "^26.6.3",
- "jest-preset-preact": "^4.0.2",
"mocha": "^9.2.0",
"preact-render-to-string": "^5.2.6",
"rimraf": "^3.0.2",
"sass": "1.56.1",
+ "source-map-support": "^0.5.21",
"typedoc": "^0.20.36",
"typescript": "4.8.4"
}
-} \ No newline at end of file
+}
diff --git a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
index 8ac5c698b..1c55572bb 100644
--- a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
+++ b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
@@ -28,7 +28,7 @@ import { Loading } from "./components/exception/loading.js";
import { Menu, NotificationCard } from "./components/menu/index.js";
import { useBackendContext } from "./context/backend.js";
import { InstanceContextProvider } from "./context/instance.js";
-import { HttpError } from "./hooks/backend.js";
+import { HttpError } from "./utils/request.js";
import {
useBackendDefaultToken,
useBackendInstanceToken,
@@ -484,7 +484,7 @@ export function Redirect({ to }: { to: string }): null {
function AdminInstanceUpdatePage({
id,
...rest
-}: { id: string } & InstanceUpdatePageProps) {
+}: { id: string } & InstanceUpdatePageProps): VNode {
const [token, changeToken] = useBackendInstanceToken(id);
const { updateLoginStatus: changeBackend } = useBackendContext();
const updateLoginStatus = (url: string, token?: string) => {
diff --git a/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx
index 7bf39152b..68f79bc35 100644
--- a/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx
+++ b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx
@@ -195,7 +195,7 @@ export function InputPaytoForm<T>({
if (opt_value) url.searchParams.set(opt_key, opt_value);
});
}
- const paytoURL = !url ? "" : url.toString();
+ const paytoURL = !url ? "" : url.href;
const errors: FormErrors<Entity> = {
target: value.target === noTargetValue ? i18n.str`required` : undefined,
diff --git a/packages/merchant-backoffice-ui/tests/__mocks__/browserMocks.ts b/packages/merchant-backoffice-ui/src/context/api.ts
index 98a5153de..81586bd35 100644
--- a/packages/merchant-backoffice-ui/tests/__mocks__/browserMocks.ts
+++ b/packages/merchant-backoffice-ui/src/context/api.ts
@@ -14,29 +14,30 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
- /**
+/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
-// Mock Browser API's which are not supported by JSDOM, e.g. ServiceWorker, LocalStorage
-/**
- * An example how to mock localStorage is given below 👇
- */
-
-/*
-// Mocks localStorage
-const localStorageMock = (function() {
- let store = {};
-
- return {
- getItem: (key) => store[key] || null,
- setItem: (key, value) => store[key] = value.toString(),
- clear: () => store = {}
- };
-
-})();
-
-Object.defineProperty(window, 'localStorage', {
- value: localStorageMock
-}); */
+import { ComponentChildren, createContext, h, VNode } from "preact";
+import { useContext } from "preact/hooks";
+import { defaultRequestHandler } from "../utils/request.js";
+
+interface Type {
+ request: typeof defaultRequestHandler;
+}
+
+const Context = createContext<Type>({
+ request: defaultRequestHandler,
+});
+
+export const useApiContext = (): Type => useContext(Context);
+export const ApiContextProvider = ({
+ children,
+ value,
+}: {
+ value: Type;
+ children: ComponentChildren;
+}): VNode => {
+ return h(Context.Provider, { value, children });
+};
diff --git a/packages/merchant-backoffice-ui/src/context/backend.test.ts b/packages/merchant-backoffice-ui/src/context/backend.test.ts
new file mode 100644
index 000000000..c7fb19293
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/context/backend.test.ts
@@ -0,0 +1,131 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { ComponentChildren, h, VNode } from "preact";
+import { MerchantBackend } from "../declaration.js";
+import {
+ useAdminAPI,
+ useInstanceAPI,
+ useManagementAPI,
+} from "../hooks/instance.js";
+import { expect } from "chai";
+import { ApiMockEnvironment } from "../hooks/testing.js";
+import { API_CREATE_INSTANCE, API_UPDATE_CURRENT_INSTANCE_AUTH, API_UPDATE_INSTANCE_AUTH_BY_ID } from "../hooks/urls.js";
+
+interface TestingContextProps {
+ children?: ComponentChildren;
+}
+
+describe("backend context api ", () => {
+
+ it("should use new token after updating the instance token in the settings as user", async () => {
+ const env = new ApiMockEnvironment();
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const instance = useInstanceAPI();
+ const management = useManagementAPI("default");
+ const admin = useAdminAPI();
+
+ return { instance, management, admin };
+ },
+ {},
+ [
+ ({ instance, management, admin }) => {
+ env.addRequestExpectation(API_UPDATE_INSTANCE_AUTH_BY_ID("default"), {
+ request: {
+ method: "token",
+ token: "another_token",
+ },
+ response: {
+ name: "instance_name",
+ } as MerchantBackend.Instances.QueryInstancesResponse,
+ });
+
+ management.setNewToken("another_token")
+ },
+ ({ instance, management, admin }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ env.addRequestExpectation(API_CREATE_INSTANCE, {
+ auth: "another_token",
+ request: {
+ id: "new_instance_id",
+ } as MerchantBackend.Instances.InstanceConfigurationMessage,
+ });
+
+ admin.createInstance({
+ id: "new_instance_id",
+ } as MerchantBackend.Instances.InstanceConfigurationMessage);
+
+ },
+ ], env.buildTestingContext());
+
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ });
+
+ it("should use new token after updating the instance token in the settings as admin", async () => {
+ const env = new ApiMockEnvironment();
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const instance = useInstanceAPI();
+ const management = useManagementAPI("default");
+ const admin = useAdminAPI();
+
+ return { instance, management, admin };
+ },
+ {},
+ [
+ ({ instance, management, admin }) => {
+ env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE_AUTH, {
+ request: {
+ method: "token",
+ token: "another_token",
+ },
+ response: {
+ name: "instance_name",
+ } as MerchantBackend.Instances.QueryInstancesResponse,
+ });
+ instance.setNewToken("another_token");
+ },
+ ({ instance, management, admin }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ env.addRequestExpectation(API_CREATE_INSTANCE, {
+ auth: "another_token",
+ request: {
+ id: "new_instance_id",
+ } as MerchantBackend.Instances.InstanceConfigurationMessage,
+ });
+
+ admin.createInstance({
+ id: "new_instance_id",
+ } as MerchantBackend.Instances.InstanceConfigurationMessage);
+ },
+ ], env.buildTestingContext());
+
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ });
+});
diff --git a/packages/merchant-backoffice-ui/src/context/backend.ts b/packages/merchant-backoffice-ui/src/context/backend.ts
index f8d1bc397..f7f8afea6 100644
--- a/packages/merchant-backoffice-ui/src/context/backend.ts
+++ b/packages/merchant-backoffice-ui/src/context/backend.ts
@@ -31,6 +31,7 @@ interface BackendContextType {
clearAllTokens: () => void;
addTokenCleaner: (c: () => void) => void;
updateLoginStatus: (url: string, token?: string) => void;
+ updateToken: (token?: string) => void;
}
const BackendContext = createContext<BackendContextType>({
@@ -41,6 +42,7 @@ const BackendContext = createContext<BackendContextType>({
clearAllTokens: () => null,
addTokenCleaner: () => null,
updateLoginStatus: () => null,
+ updateToken: () => null,
});
function useBackendContextState(
@@ -87,6 +89,7 @@ function useBackendContextState(
updateLoginStatus,
resetBackend,
clearAllTokens,
+ updateToken,
addTokenCleaner: addTokenCleanerMemo,
};
}
diff --git a/packages/merchant-backoffice-ui/src/context/fetch.ts b/packages/merchant-backoffice-ui/src/context/fetch.ts
deleted file mode 100644
index 88c9bc30c..000000000
--- a/packages/merchant-backoffice-ui/src/context/fetch.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { h, createContext, VNode, ComponentChildren } from "preact";
-import { useContext } from "preact/hooks";
-import useSWR from "swr";
-import useSWRInfinite from "swr/infinite";
-
-interface Type {
- useSWR: typeof useSWR;
- useSWRInfinite: typeof useSWRInfinite;
-}
-
-const Context = createContext<Type>({} as Type);
-
-export const useFetchContext = (): Type => useContext(Context);
-export const FetchContextProvider = ({
- children,
-}: {
- children: ComponentChildren;
-}): VNode => {
- return h(Context.Provider, { value: { useSWR, useSWRInfinite }, children });
-};
-
-export const FetchContextProviderTesting = ({
- children,
- data,
-}: {
- children: ComponentChildren;
- data: any;
-}): VNode => {
- return h(Context.Provider, {
- value: { useSWR: () => data, useSWRInfinite },
- children,
- });
-};
diff --git a/packages/merchant-backoffice-ui/src/hooks/async.ts b/packages/merchant-backoffice-ui/src/hooks/async.ts
index 6c116e628..f22badc88 100644
--- a/packages/merchant-backoffice-ui/src/hooks/async.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/async.ts
@@ -19,7 +19,6 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { useState } from "preact/hooks";
-import { cancelPendingRequest } from "./backend.js";
export interface Options {
slowTolerance: number;
@@ -62,8 +61,7 @@ export function useAsync<T>(
clearTimeout(handler);
};
- function cancel() {
- cancelPendingRequest();
+ function cancel(): void {
setLoading(false);
setSlow(false);
}
diff --git a/packages/merchant-backoffice-ui/src/hooks/backend.ts b/packages/merchant-backoffice-ui/src/hooks/backend.ts
index cbfac35de..a0639a4a0 100644
--- a/packages/merchant-backoffice-ui/src/hooks/backend.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/backend.ts
@@ -20,15 +20,16 @@
*/
import { useSWRConfig } from "swr";
-import axios, { AxiosError, AxiosResponse } from "axios";
import { MerchantBackend } from "../declaration.js";
import { useBackendContext } from "../context/backend.js";
-import { useEffect, useState } from "preact/hooks";
-import { DEFAULT_REQUEST_TIMEOUT } from "../utils/constants.js";
+import { useCallback, useEffect, useState } from "preact/hooks";
+import { useInstanceContext } from "../context/instance.js";
import {
- axiosHandler,
- removeAxiosCancelToken,
-} from "../utils/switchableAxios.js";
+ HttpResponse,
+ HttpResponseOk,
+ RequestOptions,
+} from "../utils/request.js";
+import { useApiContext } from "../context/api.js";
export function useMatchMutate(): (
re: RegExp,
@@ -44,9 +45,7 @@ export function useMatchMutate(): (
return function matchRegexMutate(re: RegExp, value?: unknown) {
const allKeys = Array.from(cache.keys());
- // console.log(allKeys)
const keys = allKeys.filter((key) => re.test(key));
- // console.log(allKeys.length, keys.length)
const mutations = keys.map((key) => {
// console.log(key)
mutate(key, value, true);
@@ -55,268 +54,234 @@ export function useMatchMutate(): (
};
}
-export type HttpResponse<T> =
- | HttpResponseOk<T>
- | HttpResponseLoading<T>
- | HttpError;
-export type HttpResponsePaginated<T> =
- | HttpResponseOkPaginated<T>
- | HttpResponseLoading<T>
- | HttpError;
-
-export interface RequestInfo {
- url: string;
- hasToken: boolean;
- params: unknown;
- data: unknown;
- status: number;
-}
-
-interface HttpResponseLoading<T> {
- ok?: false;
- loading: true;
- clientError?: false;
- serverError?: false;
-
- data?: T;
-}
-export interface HttpResponseOk<T> {
- ok: true;
- loading?: false;
- clientError?: false;
- serverError?: false;
-
- data: T;
- info?: RequestInfo;
-}
-
-export type HttpResponseOkPaginated<T> = HttpResponseOk<T> & WithPagination;
-
-export interface WithPagination {
- loadMore: () => void;
- loadMorePrev: () => void;
- isReachingEnd?: boolean;
- isReachingStart?: boolean;
-}
-
-export type HttpError =
- | HttpResponseClientError
- | HttpResponseServerError
- | HttpResponseUnexpectedError;
-export interface SwrError {
- info: unknown;
- status: number;
- message: string;
-}
-export interface HttpResponseServerError {
- ok?: false;
- loading?: false;
- clientError?: false;
- serverError: true;
-
- error?: MerchantBackend.ErrorDetail;
- status: number;
- message: string;
- info?: RequestInfo;
-}
-interface HttpResponseClientError {
- ok?: false;
- loading?: false;
- clientError: true;
- serverError?: false;
-
- info?: RequestInfo;
- isUnauthorized: boolean;
- isNotfound: boolean;
- status: number;
- error?: MerchantBackend.ErrorDetail;
- message: string;
-}
-
-interface HttpResponseUnexpectedError {
- ok?: false;
- loading?: false;
- clientError?: false;
- serverError?: false;
-
- info?: RequestInfo;
- status?: number;
- error: unknown;
- message: string;
-}
-
-type Methods = "get" | "post" | "patch" | "delete" | "put";
-
-interface RequestOptions {
- method?: Methods;
- token?: string;
- data?: unknown;
- params?: unknown;
-}
-
-function buildRequestOk<T>(
- res: AxiosResponse<T>,
- url: string,
- hasToken: boolean,
-): HttpResponseOk<T> {
- return {
- ok: true,
- data: res.data,
- info: {
- params: res.config.params,
- data: res.config.data,
- url,
- hasToken,
- status: res.status,
- },
- };
-}
-
-// function buildResponse<T>(data?: T, error?: MerchantBackend.ErrorDetail, isValidating?: boolean): HttpResponse<T> {
-// if (isValidating) return {loading: true}
-// if (error) return buildRequestFailed()
-// }
-
-function buildRequestFailed(
- ex: AxiosError<MerchantBackend.ErrorDetail>,
- url: string,
- hasToken: boolean,
-):
- | HttpResponseClientError
- | HttpResponseServerError
- | HttpResponseUnexpectedError {
- const status = ex.response?.status;
-
- const info: RequestInfo = {
- data: ex.request?.data,
- params: ex.request?.params,
- url,
- hasToken,
- status: status || 0,
- };
-
- if (status && status >= 400 && status < 500) {
- const error: HttpResponseClientError = {
- clientError: true,
- isNotfound: status === 404,
- isUnauthorized: status === 401,
- status,
- info,
- message: ex.response?.data?.hint || ex.message,
- error: ex.response?.data,
- };
- return error;
- }
- if (status && status >= 500 && status < 600) {
- const error: HttpResponseServerError = {
- serverError: true,
- status,
- info,
- message:
- `${ex.response?.data?.hint} (code ${ex.response?.data?.code})` ||
- ex.message,
- error: ex.response?.data,
- };
- return error;
- }
-
- const error: HttpResponseUnexpectedError = {
- info,
- status,
- error: ex,
- message: ex.message,
- };
-
- return error;
-}
-
-const CancelToken = axios.CancelToken;
-let source = CancelToken.source();
-
-export function cancelPendingRequest(): void {
- source.cancel("canceled by the user");
- source = CancelToken.source();
-}
-
-export function isAxiosError<T>(
- error: AxiosError | any,
-): error is AxiosError<T> {
- return error && error.isAxiosError;
-}
-
-export async function request<T>(
- url: string,
- options: RequestOptions = {},
-): Promise<HttpResponseOk<T>> {
- const headers = options.token
- ? { Authorization: `Bearer ${options.token}` }
- : undefined;
-
- try {
- const res = await axiosHandler({
- url,
- responseType: "json",
- headers,
- cancelToken: !removeAxiosCancelToken ? source.token : undefined,
- method: options.method || "get",
- data: options.data,
- params: options.params,
- timeout: DEFAULT_REQUEST_TIMEOUT * 1000,
- });
- return buildRequestOk<T>(res, url, !!options.token);
- } catch (e) {
- if (isAxiosError<MerchantBackend.ErrorDetail>(e)) {
- const error = buildRequestFailed(e, url, !!options.token);
- throw error;
- }
- throw e;
- }
-}
-
-export function multiFetcher<T>(
- urls: string[],
- token: string,
- backend: string,
-): Promise<HttpResponseOk<T>[]> {
- return Promise.all(urls.map((url) => fetcher<T>(url, token, backend)));
-}
-
-export function fetcher<T>(
- url: string,
- token: string,
- backend: string,
-): Promise<HttpResponseOk<T>> {
- return request<T>(`${backend}${url}`, { token });
-}
-
export function useBackendInstancesTestForAdmin(): HttpResponse<MerchantBackend.Instances.InstancesResponse> {
- const { url, token } = useBackendContext();
+ const { request } = useBackendBaseRequest();
type Type = MerchantBackend.Instances.InstancesResponse;
const [result, setResult] = useState<HttpResponse<Type>>({ loading: true });
useEffect(() => {
- request<Type>(`${url}/management/instances`, { token })
+ request<Type>(`/management/instances`)
.then((data) => setResult(data))
.catch((error) => setResult(error));
- }, [url, token]);
+ }, [request]);
return result;
}
export function useBackendConfig(): HttpResponse<MerchantBackend.VersionResponse> {
- const { url, token } = useBackendContext();
+ const { request } = useBackendBaseRequest();
type Type = MerchantBackend.VersionResponse;
const [result, setResult] = useState<HttpResponse<Type>>({ loading: true });
useEffect(() => {
- request<Type>(`${url}/config`, { token })
+ request<Type>(`/config`)
.then((data) => setResult(data))
.catch((error) => setResult(error));
- }, [url, token]);
+ }, [request]);
return result;
}
+
+interface useBackendInstanceRequestType {
+ request: <T>(
+ path: string,
+ options?: RequestOptions,
+ ) => Promise<HttpResponseOk<T>>;
+ fetcher: <T>(path: string) => Promise<HttpResponseOk<T>>;
+ reserveDetailFetcher: <T>(path: string) => Promise<HttpResponseOk<T>>;
+ tipsDetailFetcher: <T>(path: string) => Promise<HttpResponseOk<T>>;
+ multiFetcher: <T>(url: string[]) => Promise<HttpResponseOk<T>[]>;
+ orderFetcher: <T>(
+ path: string,
+ paid?: YesOrNo,
+ refunded?: YesOrNo,
+ wired?: YesOrNo,
+ searchDate?: Date,
+ delta?: number,
+ ) => Promise<HttpResponseOk<T>>;
+ transferFetcher: <T>(
+ path: string,
+ payto_uri?: string,
+ verified?: string,
+ position?: string,
+ delta?: number,
+ ) => Promise<HttpResponseOk<T>>;
+ templateFetcher: <T>(
+ path: string,
+ position?: string,
+ delta?: number,
+ ) => Promise<HttpResponseOk<T>>;
+}
+interface useBackendBaseRequestType {
+ request: <T>(
+ path: string,
+ options?: RequestOptions,
+ ) => Promise<HttpResponseOk<T>>;
+}
+
+type YesOrNo = "yes" | "no";
+
+/**
+ *
+ * @param root the request is intended to the base URL and no the instance URL
+ * @returns request handler to
+ */
+export function useBackendBaseRequest(): useBackendBaseRequestType {
+ const { url: backend, token } = useBackendContext();
+ const { request: requestHandler } = useApiContext();
+
+ const request = useCallback(
+ function requestImpl<T>(
+ path: string,
+ options: RequestOptions = {},
+ ): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(backend, path, { token, ...options });
+ },
+ [backend, token],
+ );
+
+ return { request };
+}
+
+export function useBackendInstanceRequest(): useBackendInstanceRequestType {
+ const { url: baseUrl, token: baseToken } = useBackendContext();
+ const { token: instanceToken, id, admin } = useInstanceContext();
+ const { request: requestHandler } = useApiContext();
+
+ const { backend, token } = !admin
+ ? { backend: baseUrl, token: baseToken }
+ : { backend: `${baseUrl}/instances/${id}`, token: instanceToken };
+
+ const request = useCallback(
+ function requestImpl<T>(
+ path: string,
+ options: RequestOptions = {},
+ ): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(backend, path, { token, ...options });
+ },
+ [backend, token],
+ );
+
+ const multiFetcher = useCallback(
+ function multiFetcherImpl<T>(
+ paths: string[],
+ ): Promise<HttpResponseOk<T>[]> {
+ return Promise.all(
+ paths.map((path) => requestHandler<T>(backend, path, { token })),
+ );
+ },
+ [backend, token],
+ );
+
+ const fetcher = useCallback(
+ function fetcherImpl<T>(path: string): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(backend, path, { token });
+ },
+ [backend, token],
+ );
+
+ const orderFetcher = useCallback(
+ function orderFetcherImpl<T>(
+ path: string,
+ paid?: YesOrNo,
+ refunded?: YesOrNo,
+ wired?: YesOrNo,
+ searchDate?: Date,
+ delta?: number,
+ ): Promise<HttpResponseOk<T>> {
+ const date_ms =
+ delta && delta < 0 && searchDate
+ ? searchDate.getTime() + 1
+ : searchDate?.getTime();
+ const params: any = {};
+ if (paid !== undefined) params.paid = paid;
+ if (delta !== undefined) params.delta = delta;
+ if (refunded !== undefined) params.refunded = refunded;
+ if (wired !== undefined) params.wired = wired;
+ if (date_ms !== undefined) params.date_ms = date_ms;
+ return requestHandler<T>(backend, path, { params, token });
+ },
+ [backend, token],
+ );
+
+ const reserveDetailFetcher = useCallback(
+ function reserveDetailFetcherImpl<T>(
+ path: string,
+ ): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(backend, path, {
+ params: {
+ tips: "yes",
+ },
+ token,
+ });
+ },
+ [backend, token],
+ );
+
+ const tipsDetailFetcher = useCallback(
+ function tipsDetailFetcherImpl<T>(
+ path: string,
+ ): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(backend, path, {
+ params: {
+ pickups: "yes",
+ },
+ token,
+ });
+ },
+ [backend, token],
+ );
+
+ const transferFetcher = useCallback(
+ function transferFetcherImpl<T>(
+ path: string,
+ payto_uri?: string,
+ verified?: string,
+ position?: string,
+ delta?: number,
+ ): Promise<HttpResponseOk<T>> {
+ const params: any = {};
+ if (payto_uri !== undefined) params.payto_uri = payto_uri;
+ if (verified !== undefined) params.verified = verified;
+ if (delta !== undefined) {
+ params.limit = delta;
+ }
+ if (position !== undefined) params.offset = position;
+
+ return requestHandler<T>(backend, path, { params, token });
+ },
+ [backend, token],
+ );
+
+ const templateFetcher = useCallback(
+ function templateFetcherImpl<T>(
+ path: string,
+ position?: string,
+ delta?: number,
+ ): Promise<HttpResponseOk<T>> {
+ const params: any = {};
+ if (delta !== undefined) {
+ params.limit = delta;
+ }
+ if (position !== undefined) params.offset = position;
+
+ return requestHandler<T>(backend, path, { params, token });
+ },
+ [backend, token],
+ );
+
+ return {
+ request,
+ fetcher,
+ multiFetcher,
+ orderFetcher,
+ reserveDetailFetcher,
+ tipsDetailFetcher,
+ transferFetcher,
+ templateFetcher,
+ };
+}
diff --git a/packages/merchant-backoffice-ui/src/hooks/index.ts b/packages/merchant-backoffice-ui/src/hooks/index.ts
index 0581d9938..bb210c9ba 100644
--- a/packages/merchant-backoffice-ui/src/hooks/index.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/index.ts
@@ -59,6 +59,7 @@ export function useBackendDefaultToken(
export function useBackendInstanceToken(
id: string,
): [string | undefined, StateUpdater<string | undefined>] {
+ const [random, setRandom] = useState(0);
const [token, setToken] = useLocalStorage(`backend-token-${id}`);
const [defaultToken, defaultSetToken] = useBackendDefaultToken();
@@ -66,8 +67,20 @@ export function useBackendInstanceToken(
if (id === "default") {
return [defaultToken, defaultSetToken];
}
+ function updateToken(
+ value:
+ | (string | undefined)
+ | ((s: string | undefined) => string | undefined),
+ ): void {
+ setToken((p) => {
+ const toStore = value instanceof Function ? value(p) : value;
+ // setToken(value)
+ setRandom(new Date().getTime());
+ return toStore;
+ });
+ }
- return [token, setToken];
+ return [token, updateToken];
}
export function useLang(initial?: string): [string, StateUpdater<string>] {
diff --git a/packages/merchant-backoffice-ui/src/hooks/instance.test.ts b/packages/merchant-backoffice-ui/src/hooks/instance.test.ts
new file mode 100644
index 000000000..c7aa63e20
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/instance.test.ts
@@ -0,0 +1,660 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { expect } from "chai";
+import { MerchantBackend } from "../declaration.js";
+import { useAdminAPI, useBackendInstances, useInstanceAPI, useInstanceDetails, useManagementAPI } from "./instance.js";
+import { ApiMockEnvironment } from "./testing.js";
+import {
+ API_CREATE_INSTANCE,
+ API_DELETE_INSTANCE,
+ API_GET_CURRENT_INSTANCE,
+ API_LIST_INSTANCES,
+ API_UPDATE_CURRENT_INSTANCE,
+ API_UPDATE_CURRENT_INSTANCE_AUTH, API_UPDATE_INSTANCE_BY_ID
+} from "./urls.js";
+
+describe("instance api interaction with details", () => {
+
+ it("should evict cache when updating an instance", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
+ response: {
+ name: 'instance_name'
+ } as MerchantBackend.Instances.QueryInstancesResponse,
+ });
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const api = useInstanceAPI();
+ const query = useInstanceDetails();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ name: 'instance_name'
+ });
+ env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE, {
+ request: {
+ name: 'other_name'
+ } as MerchantBackend.Instances.InstanceReconfigurationMessage,
+ });
+ env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
+ response: {
+ name: 'other_name'
+ } as MerchantBackend.Instances.QueryInstancesResponse,
+ });
+ api.updateInstance({
+ name: 'other_name'
+ } as MerchantBackend.Instances.InstanceReconfigurationMessage);
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ name: 'other_name'
+ });
+ },
+ ], env.buildTestingContext());
+
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ });
+
+ it("should evict cache when setting the instance's token", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
+ response: {
+ name: 'instance_name',
+ auth: {
+ method: 'token',
+ token: 'not-secret',
+ }
+ } as MerchantBackend.Instances.QueryInstancesResponse,
+ });
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const api = useInstanceAPI();
+ const query = useInstanceDetails();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ name: 'instance_name',
+ auth: {
+ method: 'token',
+ token: 'not-secret',
+ }
+ });
+ env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE_AUTH, {
+ request: {
+ method: 'token',
+ token: 'secret'
+ } as MerchantBackend.Instances.InstanceAuthConfigurationMessage,
+ });
+ env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
+ response: {
+ name: 'instance_name',
+ auth: {
+ method: 'token',
+ token: 'secret',
+ }
+ } as MerchantBackend.Instances.QueryInstancesResponse,
+ });
+ api.setNewToken('secret')
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ name: 'instance_name',
+ auth: {
+ method: 'token',
+ token: 'secret',
+ }
+ });
+ },
+ ], env.buildTestingContext());
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ });
+
+ it("should evict cache when clearing the instance's token", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
+ response: {
+ name: 'instance_name',
+ auth: {
+ method: 'token',
+ token: 'not-secret',
+ }
+ } as MerchantBackend.Instances.QueryInstancesResponse,
+ });
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const api = useInstanceAPI();
+ const query = useInstanceDetails();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ name: 'instance_name',
+ auth: {
+ method: 'token',
+ token: 'not-secret',
+ }
+ });
+ env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE_AUTH, {
+ request: {
+ method: 'external',
+ } as MerchantBackend.Instances.InstanceAuthConfigurationMessage,
+ });
+ env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
+ response: {
+ name: 'instance_name',
+ auth: {
+ method: 'external',
+ }
+ } as MerchantBackend.Instances.QueryInstancesResponse,
+ });
+
+ api.clearToken();
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ name: 'instance_name',
+ auth: {
+ method: 'external',
+ }
+ });
+ },
+ ], env.buildTestingContext());
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ // const { result, waitForNextUpdate } = renderHook(
+ // () => {
+ // const api = useInstanceAPI();
+ // const query = useInstanceDetails();
+
+ // return { query, api };
+ // },
+ // { wrapper: TestingContext }
+ // );
+
+ // expect(result.current).not.undefined;
+ // if (!result.current) {
+ // return;
+ // }
+ // expect(result.current.query.loading).true;
+
+ // await waitForNextUpdate({ timeout: 1 });
+
+ // expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ // expect(result.current.query.loading).false;
+
+ // expect(result.current?.query.ok).true;
+ // if (!result.current?.query.ok) return;
+
+ // expect(result.current.query.data).equals({
+ // name: 'instance_name',
+ // auth: {
+ // method: 'token',
+ // token: 'not-secret',
+ // }
+ // });
+
+
+ // act(async () => {
+ // await result.current?.api.clearToken();
+ // });
+
+ // expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+
+ // expect(result.current.query.loading).false;
+
+ // await waitForNextUpdate({ timeout: 1 });
+
+ // expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ // expect(result.current.query.loading).false;
+ // expect(result.current.query.ok).true;
+
+ // expect(result.current.query.data).equals({
+ // name: 'instance_name',
+ // auth: {
+ // method: 'external',
+ // }
+ // });
+ });
+});
+
+describe("instance admin api interaction with listing", () => {
+
+ it("should evict cache when creating a new instance", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_LIST_INSTANCES, {
+ response: {
+ instances: [{
+ name: 'instance_name'
+ } as MerchantBackend.Instances.Instance]
+ },
+ });
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const api = useAdminAPI();
+ const query = useBackendInstances();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ instances: [{
+ name: 'instance_name'
+ }]
+ });
+
+ env.addRequestExpectation(API_CREATE_INSTANCE, {
+ request: {
+ name: 'other_name'
+ } as MerchantBackend.Instances.InstanceConfigurationMessage,
+ });
+ env.addRequestExpectation(API_LIST_INSTANCES, {
+ response: {
+ instances: [{
+ name: 'instance_name'
+ } as MerchantBackend.Instances.Instance,
+ {
+ name: 'other_name'
+ } as MerchantBackend.Instances.Instance]
+ },
+ });
+
+ api.createInstance({
+ name: 'other_name'
+ } as MerchantBackend.Instances.InstanceConfigurationMessage);
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ instances: [{
+ name: 'instance_name'
+ }, {
+ name: 'other_name'
+ }]
+ });
+ },
+ ], env.buildTestingContext());
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ });
+
+ it("should evict cache when deleting an instance", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_LIST_INSTANCES, {
+ response: {
+ instances: [{
+ id: 'default',
+ name: 'instance_name'
+ } as MerchantBackend.Instances.Instance,
+ {
+ id: 'the_id',
+ name: 'second_instance'
+ } as MerchantBackend.Instances.Instance]
+ },
+ });
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const api = useAdminAPI();
+ const query = useBackendInstances();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ instances: [{
+ id: 'default',
+ name: 'instance_name'
+ }, {
+ id: 'the_id',
+ name: 'second_instance'
+ }]
+ });
+
+ env.addRequestExpectation(API_DELETE_INSTANCE('the_id'), {});
+ env.addRequestExpectation(API_LIST_INSTANCES, {
+ response: {
+ instances: [{
+ id: 'default',
+ name: 'instance_name'
+ } as MerchantBackend.Instances.Instance]
+ },
+ });
+
+ api.deleteInstance('the_id');
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ instances: [{
+ id: 'default',
+ name: 'instance_name'
+ }]
+ });
+ },
+ ], env.buildTestingContext());
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ // const { result, waitForNextUpdate } = renderHook(
+ // () => {
+ // const api = useAdminAPI();
+ // const query = useBackendInstances();
+
+ // return { query, api };
+ // },
+ // { wrapper: TestingContext }
+ // );
+
+ // expect(result.current).not.undefined;
+ // if (!result.current) {
+ // return;
+ // }
+ // expect(result.current.query.loading).true;
+
+ // await waitForNextUpdate({ timeout: 1 });
+
+ // expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ // expect(result.current.query.loading).false;
+
+ // expect(result.current?.query.ok).true;
+ // if (!result.current?.query.ok) return;
+
+ // expect(result.current.query.data).equals({
+ // instances: [{
+ // id: 'default',
+ // name: 'instance_name'
+ // }, {
+ // id: 'the_id',
+ // name: 'second_instance'
+ // }]
+ // });
+
+ // env.addRequestExpectation(API_DELETE_INSTANCE('the_id'), {});
+
+ // act(async () => {
+ // await result.current?.api.deleteInstance('the_id');
+ // });
+
+ // expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ // env.addRequestExpectation(API_LIST_INSTANCES, {
+ // response: {
+ // instances: [{
+ // id: 'default',
+ // name: 'instance_name'
+ // } as MerchantBackend.Instances.Instance]
+ // },
+ // });
+
+ // expect(result.current.query.loading).false;
+
+ // await waitForNextUpdate({ timeout: 1 });
+
+ // expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ // expect(result.current.query.loading).false;
+ // expect(result.current.query.ok).true;
+
+ // expect(result.current.query.data).equals({
+ // instances: [{
+ // id: 'default',
+ // name: 'instance_name'
+ // }]
+ // });
+ });
+
+ it("should evict cache when deleting (purge) an instance", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_LIST_INSTANCES, {
+ response: {
+ instances: [{
+ id: 'default',
+ name: 'instance_name'
+ } as MerchantBackend.Instances.Instance,
+ {
+ id: 'the_id',
+ name: 'second_instance'
+ } as MerchantBackend.Instances.Instance]
+ },
+ });
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const api = useAdminAPI();
+ const query = useBackendInstances();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ instances: [{
+ id: 'default',
+ name: 'instance_name'
+ }, {
+ id: 'the_id',
+ name: 'second_instance'
+ }]
+ });
+
+ env.addRequestExpectation(API_DELETE_INSTANCE('the_id'), {
+ qparam: {
+ purge: 'YES'
+ }
+ });
+ env.addRequestExpectation(API_LIST_INSTANCES, {
+ response: {
+ instances: [{
+ id: 'default',
+ name: 'instance_name'
+ } as MerchantBackend.Instances.Instance]
+ },
+ });
+
+ api.purgeInstance('the_id')
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ instances: [{
+ id: 'default',
+ name: 'instance_name'
+ }]
+ });
+ },
+ ], env.buildTestingContext());
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ });
+});
+
+describe("instance management api interaction with listing", () => {
+
+ it("should evict cache when updating an instance", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_LIST_INSTANCES, {
+ response: {
+ instances: [{
+ id: 'managed',
+ name: 'instance_name'
+ } as MerchantBackend.Instances.Instance]
+ },
+ });
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const api = useManagementAPI('managed');
+ const query = useBackendInstances();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ instances: [{
+ id: 'managed',
+ name: 'instance_name'
+ }]
+ });
+
+ env.addRequestExpectation(API_UPDATE_INSTANCE_BY_ID('managed'), {
+ request: {
+ name: 'other_name'
+ } as MerchantBackend.Instances.InstanceReconfigurationMessage,
+ });
+ env.addRequestExpectation(API_LIST_INSTANCES, {
+ response: {
+ instances: [
+ {
+ id: 'managed',
+ name: 'other_name'
+ } as MerchantBackend.Instances.Instance]
+ },
+ });
+
+ api.updateInstance({
+ name: 'other_name'
+ } as MerchantBackend.Instances.InstanceConfigurationMessage);
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ instances: [{
+ id: 'managed',
+ name: 'other_name'
+ }]
+ });
+ },
+ ], env.buildTestingContext());
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ });
+
+});
+
diff --git a/packages/merchant-backoffice-ui/src/hooks/instance.ts b/packages/merchant-backoffice-ui/src/hooks/instance.ts
index ab59487de..3c05472d0 100644
--- a/packages/merchant-backoffice-ui/src/hooks/instance.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/instance.ts
@@ -15,14 +15,11 @@
*/
import useSWR, { useSWRConfig } from "swr";
import { useBackendContext } from "../context/backend.js";
-import { useInstanceContext } from "../context/instance.js";
import { MerchantBackend } from "../declaration.js";
+import { HttpError, HttpResponse, HttpResponseOk } from "../utils/request.js";
import {
- fetcher,
- HttpError,
- HttpResponse,
- HttpResponseOk,
- request,
+ useBackendBaseRequest,
+ useBackendInstanceRequest,
useMatchMutate,
} from "./backend.js";
@@ -36,15 +33,14 @@ interface InstanceAPI {
}
export function useAdminAPI(): AdminAPI {
- const { url, token } = useBackendContext();
+ const { request } = useBackendBaseRequest();
const mutateAll = useMatchMutate();
const createInstance = async (
instance: MerchantBackend.Instances.InstanceConfigurationMessage,
): Promise<void> => {
- await request(`${url}/management/instances`, {
- method: "post",
- token,
+ await request(`/management/instances`, {
+ method: "POST",
data: instance,
});
@@ -52,18 +48,16 @@ export function useAdminAPI(): AdminAPI {
};
const deleteInstance = async (id: string): Promise<void> => {
- await request(`${url}/management/instances/${id}`, {
- method: "delete",
- token,
+ await request(`/management/instances/${id}`, {
+ method: "DELETE",
});
mutateAll(/\/management\/instances/);
};
const purgeInstance = async (id: string): Promise<void> => {
- await request(`${url}/management/instances/${id}`, {
- method: "delete",
- token,
+ await request(`/management/instances/${id}`, {
+ method: "DELETE",
params: {
purge: "YES",
},
@@ -85,14 +79,14 @@ export interface AdminAPI {
export function useManagementAPI(instanceId: string): InstanceAPI {
const mutateAll = useMatchMutate();
- const { url, token, updateLoginStatus } = useBackendContext();
+ const { updateToken } = useBackendContext();
+ const { request } = useBackendBaseRequest();
const updateInstance = async (
instance: MerchantBackend.Instances.InstanceReconfigurationMessage,
): Promise<void> => {
- await request(`${url}/management/instances/${instanceId}`, {
- method: "patch",
- token,
+ await request(`/management/instances/${instanceId}`, {
+ method: "PATCH",
data: instance,
});
@@ -100,18 +94,16 @@ export function useManagementAPI(instanceId: string): InstanceAPI {
};
const deleteInstance = async (): Promise<void> => {
- await request(`${url}/management/instances/${instanceId}`, {
- method: "delete",
- token,
+ await request(`/management/instances/${instanceId}`, {
+ method: "DELETE",
});
mutateAll(/\/management\/instances/);
};
const clearToken = async (): Promise<void> => {
- await request(`${url}/management/instances/${instanceId}/auth`, {
- method: "post",
- token,
+ await request(`/management/instances/${instanceId}/auth`, {
+ method: "POST",
data: { method: "external" },
});
@@ -119,13 +111,12 @@ export function useManagementAPI(instanceId: string): InstanceAPI {
};
const setNewToken = async (newToken: string): Promise<void> => {
- await request(`${url}/management/instances/${instanceId}/auth`, {
- method: "post",
- token,
+ await request(`/management/instances/${instanceId}/auth`, {
+ method: "POST",
data: { method: "token", token: newToken },
});
- updateLoginStatus(url, newToken);
+ updateToken(newToken);
mutateAll(/\/management\/instances/);
};
@@ -139,71 +130,59 @@ export function useInstanceAPI(): InstanceAPI {
token: adminToken,
updateLoginStatus,
} = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
-
- const { url, token } = !admin
- ? { url: baseUrl, token: adminToken }
- : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+ const { request } = useBackendInstanceRequest();
const updateInstance = async (
instance: MerchantBackend.Instances.InstanceReconfigurationMessage,
): Promise<void> => {
- await request(`${url}/private/`, {
- method: "patch",
- token,
+ await request(`/private/`, {
+ method: "PATCH",
data: instance,
});
if (adminToken) mutate(["/private/instances", adminToken, baseUrl], null);
- mutate([`/private/`, token, url], null);
+ mutate([`/private/`], null);
};
const deleteInstance = async (): Promise<void> => {
- await request(`${url}/private/`, {
- method: "delete",
- token: adminToken,
+ await request(`/private/`, {
+ method: "DELETE",
+ // token: adminToken,
});
if (adminToken) mutate(["/private/instances", adminToken, baseUrl], null);
- mutate([`/private/`, token, url], null);
+ mutate([`/private/`], null);
};
const clearToken = async (): Promise<void> => {
- await request(`${url}/private/auth`, {
- method: "post",
- token,
+ await request(`/private/auth`, {
+ method: "POST",
data: { method: "external" },
});
- mutate([`/private/`, token, url], null);
+ mutate([`/private/`], null);
};
const setNewToken = async (newToken: string): Promise<void> => {
- await request(`${url}/private/auth`, {
- method: "post",
- token,
+ await request(`/private/auth`, {
+ method: "POST",
data: { method: "token", token: newToken },
});
updateLoginStatus(baseUrl, newToken);
- mutate([`/private/`, token, url], null);
+ mutate([`/private/`], null);
};
return { updateInstance, deleteInstance, setNewToken, clearToken };
}
export function useInstanceDetails(): HttpResponse<MerchantBackend.Instances.QueryInstancesResponse> {
- const { url: baseUrl, token: baseToken } = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
-
- const { url, token } = !admin
- ? { url: baseUrl, token: baseToken }
- : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+ const { fetcher } = useBackendInstanceRequest();
const { data, error, isValidating } = useSWR<
HttpResponseOk<MerchantBackend.Instances.QueryInstancesResponse>,
HttpError
- >([`/private/`, token, url], fetcher, {
+ >([`/private/`], fetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
@@ -225,17 +204,12 @@ type KYCStatus =
| { type: "redirect"; status: MerchantBackend.Instances.AccountKycRedirects };
export function useInstanceKYCDetails(): HttpResponse<KYCStatus> {
- const { url: baseUrl, token: baseToken } = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
-
- const { url, token } = !admin
- ? { url: baseUrl, token: baseToken }
- : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+ const { fetcher } = useBackendInstanceRequest();
const { data, error } = useSWR<
HttpResponseOk<MerchantBackend.Instances.AccountKycRedirects>,
HttpError
- >([`/private/kyc`, token, url], fetcher, {
+ >([`/private/kyc`], fetcher, {
refreshInterval: 5000,
refreshWhenHidden: false,
revalidateOnFocus: false,
@@ -258,12 +232,12 @@ export function useInstanceKYCDetails(): HttpResponse<KYCStatus> {
export function useManagedInstanceDetails(
instanceId: string,
): HttpResponse<MerchantBackend.Instances.QueryInstancesResponse> {
- const { url, token } = useBackendContext();
+ const { request } = useBackendBaseRequest();
const { data, error, isValidating } = useSWR<
HttpResponseOk<MerchantBackend.Instances.QueryInstancesResponse>,
HttpError
- >([`/management/instances/${instanceId}`, token, url], fetcher, {
+ >([`/management/instances/${instanceId}`], request, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
@@ -281,13 +255,12 @@ export function useManagedInstanceDetails(
}
export function useBackendInstances(): HttpResponse<MerchantBackend.Instances.InstancesResponse> {
- const { url } = useBackendContext();
- const { token } = useInstanceContext();
+ const { request } = useBackendBaseRequest();
const { data, error, isValidating } = useSWR<
HttpResponseOk<MerchantBackend.Instances.InstancesResponse>,
HttpError
- >(["/management/instances", token, url], fetcher);
+ >(["/management/instances"], request);
if (isValidating) return { loading: true, data: data?.data };
if (data) return data;
diff --git a/packages/merchant-backoffice-ui/src/hooks/order.test.ts b/packages/merchant-backoffice-ui/src/hooks/order.test.ts
new file mode 100644
index 000000000..be4d1d804
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/order.test.ts
@@ -0,0 +1,579 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { expect } from "chai";
+import { MerchantBackend } from "../declaration.js";
+import { useInstanceOrders, useOrderAPI, useOrderDetails } from "./order.js";
+import { ApiMockEnvironment } from "./testing.js";
+import {
+ API_CREATE_ORDER,
+ API_DELETE_ORDER,
+ API_FORGET_ORDER_BY_ID,
+ API_GET_ORDER_BY_ID,
+ API_LIST_ORDERS, API_REFUND_ORDER_BY_ID
+} from "./urls.js";
+
+describe("order api interaction with listing", () => {
+
+ it("should evict cache when creating an order", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: 0, paid: "yes" },
+ response: {
+ orders: [{ order_id: "1" } as MerchantBackend.Orders.OrderHistoryEntry],
+ },
+ });
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: -20, paid: "yes" },
+ response: {
+ orders: [{ order_id: "2" } as MerchantBackend.Orders.OrderHistoryEntry],
+ },
+ });
+
+
+ const newDate = (d: Date) => {
+ //console.log("new date", d);
+ };
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const query = useInstanceOrders({ paid: "yes" }, newDate);
+ const api = useOrderAPI();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).undefined;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ orders: [{ order_id: "1" }, { order_id: "2" }],
+ });
+
+ env.addRequestExpectation(API_CREATE_ORDER, {
+ request: {
+ order: { amount: "ARS:12", summary: "pay me" },
+ },
+ response: { order_id: "3" },
+ });
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: 0, paid: "yes" },
+ response: {
+ orders: [{ order_id: "1" } as any],
+ },
+ });
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: -20, paid: "yes" },
+ response: {
+ orders: [{ order_id: "2" } as any, { order_id: "3" } as any],
+ },
+ });
+
+ api.createOrder({
+ order: { amount: "ARS:12", summary: "pay me" },
+ } as any);
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ expect(query.loading).undefined;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ orders: [{ order_id: "1" }, { order_id: "2" }, { order_id: "3" }],
+ });
+ },
+ ], env.buildTestingContext());
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ });
+
+ it("should evict cache when doing a refund", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: 0, paid: "yes" },
+ response: {
+ orders: [{ order_id: "1", amount: 'EUR:12', refundable: true } as MerchantBackend.Orders.OrderHistoryEntry],
+ },
+ });
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: -20, paid: "yes" },
+ response: { orders: [], },
+ });
+
+
+ const newDate = (d: Date) => {
+ //console.log("new date", d);
+ };
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const query = useInstanceOrders({ paid: "yes" }, newDate);
+ const api = useOrderAPI();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ expect(query.loading).undefined;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ orders: [{
+ order_id: "1",
+ amount: 'EUR:12',
+ refundable: true,
+ }],
+ });
+ env.addRequestExpectation(API_REFUND_ORDER_BY_ID('1'), {
+ request: {
+ reason: 'double pay',
+ refund: 'EUR:1'
+ },
+ });
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: 0, paid: "yes" },
+ response: {
+ orders: [{ order_id: "1", amount: 'EUR:12', refundable: false } as any],
+ },
+ });
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: -20, paid: "yes" },
+ response: { orders: [], },
+ });
+
+ api.refundOrder('1', {
+ reason: 'double pay',
+ refund: 'EUR:1'
+ });
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ expect(query.loading).undefined;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ orders: [{
+ order_id: "1",
+ amount: 'EUR:12',
+ refundable: false,
+ }],
+ });
+ },
+ ], env.buildTestingContext());
+
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ });
+
+ it("should evict cache when deleting an order", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: 0, paid: "yes" },
+ response: {
+ orders: [{ order_id: "1" } as MerchantBackend.Orders.OrderHistoryEntry],
+ },
+ });
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: -20, paid: "yes" },
+ response: {
+ orders: [{ order_id: "2" } as MerchantBackend.Orders.OrderHistoryEntry],
+ },
+ });
+
+
+ const newDate = (d: Date) => {
+ //console.log("new date", d);
+ };
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const query = useInstanceOrders({ paid: "yes" }, newDate);
+ const api = useOrderAPI();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).undefined;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ orders: [{ order_id: "1" }, { order_id: "2" }],
+ });
+
+ env.addRequestExpectation(API_DELETE_ORDER('1'), {});
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: 0, paid: "yes" },
+ response: {
+ orders: [],
+ },
+ });
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: -20, paid: "yes" },
+ response: {
+ orders: [{ order_id: "2" } as any],
+ },
+ });
+
+ api.deleteOrder('1');
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).undefined;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ orders: [{ order_id: "2" }],
+ });
+ },
+ ], env.buildTestingContext());
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ });
+
+});
+
+describe("order api interaction with details", () => {
+
+ it("should evict cache when doing a refund", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), {
+ // qparam: { delta: 0, paid: "yes" },
+ response: {
+ summary: 'description',
+ refund_amount: 'EUR:0',
+ } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse,
+ });
+
+ const newDate = (d: Date) => {
+ //console.log("new date", d);
+ };
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const query = useOrderDetails('1')
+ const api = useOrderAPI();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ summary: 'description',
+ refund_amount: 'EUR:0',
+ });
+ env.addRequestExpectation(API_REFUND_ORDER_BY_ID('1'), {
+ request: {
+ reason: 'double pay',
+ refund: 'EUR:1'
+ },
+ });
+
+ env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), {
+ response: {
+ summary: 'description',
+ refund_amount: 'EUR:1',
+ } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse,
+ });
+
+ api.refundOrder('1', {
+ reason: 'double pay',
+ refund: 'EUR:1'
+ })
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ summary: 'description',
+ refund_amount: 'EUR:1',
+ });
+ },
+ ], env.buildTestingContext());
+
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ })
+
+ it("should evict cache when doing a forget", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), {
+ // qparam: { delta: 0, paid: "yes" },
+ response: {
+ summary: 'description',
+ refund_amount: 'EUR:0',
+ } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse,
+ });
+
+ const newDate = (d: Date) => {
+ //console.log("new date", d);
+ };
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const query = useOrderDetails('1')
+ const api = useOrderAPI();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ summary: 'description',
+ refund_amount: 'EUR:0',
+ });
+ env.addRequestExpectation(API_FORGET_ORDER_BY_ID('1'), {
+ request: {
+ fields: ['$.summary']
+ },
+ });
+
+ env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), {
+ response: {
+ summary: undefined,
+ } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse,
+ });
+
+ api.forgetOrder('1', {
+ fields: ['$.summary']
+ })
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ summary: undefined,
+ });
+ },
+ ], env.buildTestingContext());
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ })
+})
+
+describe("order listing pagination", () => {
+
+ it("should not load more if has reach the end", async () => {
+ const env = new ApiMockEnvironment();
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: 20, wired: "yes", date_ms: 12 },
+ response: {
+ orders: [{ order_id: "1" } as any],
+ },
+ });
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: -20, wired: "yes", date_ms: 13 },
+ response: {
+ orders: [{ order_id: "2" } as any],
+ },
+ });
+
+
+ const newDate = (d: Date) => {
+ //console.log("new date", d);
+ };
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const date = new Date(12);
+ const query = useInstanceOrders({ wired: "yes", date }, newDate)
+ const api = useOrderAPI();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).undefined;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ orders: [{ order_id: "1" }, { order_id: "2" }],
+ });
+ expect(query.isReachingEnd).true
+ expect(query.isReachingStart).true
+
+ // should not trigger new state update or query
+ query.loadMore()
+ query.loadMorePrev();
+ },
+ ], env.buildTestingContext());
+
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ });
+
+ it("should load more if result brings more that PAGE_SIZE", async () => {
+ const env = new ApiMockEnvironment();
+
+ const ordersFrom0to20 = Array.from({ length: 20 }).map((e, i) => ({ order_id: String(i) }))
+ const ordersFrom20to40 = Array.from({ length: 20 }).map((e, i) => ({ order_id: String(i + 20) }))
+ const ordersFrom20to0 = [...ordersFrom0to20].reverse()
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: 20, wired: "yes", date_ms: 12 },
+ response: {
+ orders: ordersFrom0to20,
+ },
+ });
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: -20, wired: "yes", date_ms: 13 },
+ response: {
+ orders: ordersFrom20to40,
+ },
+ });
+
+ const newDate = (d: Date) => {
+ //console.log("new date", d);
+ };
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const date = new Date(12);
+ const query = useInstanceOrders({ wired: "yes", date }, newDate)
+ const api = useOrderAPI();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).undefined;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ orders: [...ordersFrom20to0, ...ordersFrom20to40],
+ });
+ expect(query.isReachingEnd).false
+ expect(query.isReachingStart).false
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: -40, wired: "yes", date_ms: 13 },
+ response: {
+ orders: [...ordersFrom20to40, { order_id: '41' }],
+ },
+ });
+
+ query.loadMore()
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(query.loading).undefined;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ orders: [...ordersFrom20to0, ...ordersFrom20to40, { order_id: '41' }],
+ });
+
+ env.addRequestExpectation(API_LIST_ORDERS, {
+ qparam: { delta: 40, wired: "yes", date_ms: 12 },
+ response: {
+ orders: [...ordersFrom0to20, { order_id: '-1' }],
+ },
+ });
+
+ query.loadMorePrev()
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(query.loading).undefined;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ orders: [{ order_id: '-1' }, ...ordersFrom20to0, ...ordersFrom20to40, { order_id: '41' }],
+ });
+ },
+ ], env.buildTestingContext());
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ });
+
+
+});
diff --git a/packages/merchant-backoffice-ui/src/hooks/order.ts b/packages/merchant-backoffice-ui/src/hooks/order.ts
index d1e26b671..0bea6b963 100644
--- a/packages/merchant-backoffice-ui/src/hooks/order.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/order.ts
@@ -14,20 +14,16 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { useEffect, useState } from "preact/hooks";
-import useSWR, { useSWRConfig } from "swr";
-import { useBackendContext } from "../context/backend.js";
-import { useInstanceContext } from "../context/instance.js";
+import useSWR from "swr";
import { MerchantBackend } from "../declaration.js";
import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js";
import {
- fetcher,
HttpError,
HttpResponse,
HttpResponseOk,
HttpResponsePaginated,
- request,
- useMatchMutate,
-} from "./backend.js";
+} from "../utils/request.js";
+import { useBackendInstanceRequest, useMatchMutate } from "./backend.js";
export interface OrderAPI {
//FIXME: add OutOfStockResponse on 410
@@ -48,52 +44,17 @@ export interface OrderAPI {
type YesOrNo = "yes" | "no";
-export function orderFetcher<T>(
- url: string,
- token: string,
- backend: string,
- paid?: YesOrNo,
- refunded?: YesOrNo,
- wired?: YesOrNo,
- searchDate?: Date,
- delta?: number,
-): Promise<HttpResponseOk<T>> {
- const date_ms =
- delta && delta < 0 && searchDate
- ? searchDate.getTime() + 1
- : searchDate?.getTime();
- const params: any = {};
- if (paid !== undefined) params.paid = paid;
- if (delta !== undefined) params.delta = delta;
- if (refunded !== undefined) params.refunded = refunded;
- if (wired !== undefined) params.wired = wired;
- if (date_ms !== undefined) params.date_ms = date_ms;
- return request<T>(`${backend}${url}`, { token, params });
-}
-
export function useOrderAPI(): OrderAPI {
const mutateAll = useMatchMutate();
- const { url: baseUrl, token: adminToken } = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
-
- const { url, token } = !admin
- ? {
- url: baseUrl,
- token: adminToken,
- }
- : {
- url: `${baseUrl}/instances/${id}`,
- token: instanceToken,
- };
+ const { request } = useBackendInstanceRequest();
const createOrder = async (
data: MerchantBackend.Orders.PostOrderRequest,
): Promise<HttpResponseOk<MerchantBackend.Orders.PostOrderResponse>> => {
const res = await request<MerchantBackend.Orders.PostOrderResponse>(
- `${url}/private/orders`,
+ `/private/orders`,
{
- method: "post",
- token,
+ method: "POST",
data,
},
);
@@ -107,10 +68,9 @@ export function useOrderAPI(): OrderAPI {
): Promise<HttpResponseOk<MerchantBackend.Orders.MerchantRefundResponse>> => {
mutateAll(/@"\/private\/orders"@/);
const res = request<MerchantBackend.Orders.MerchantRefundResponse>(
- `${url}/private/orders/${orderId}/refund`,
+ `/private/orders/${orderId}/refund`,
{
- method: "post",
- token,
+ method: "POST",
data,
},
);
@@ -125,9 +85,8 @@ export function useOrderAPI(): OrderAPI {
data: MerchantBackend.Orders.ForgetRequest,
): Promise<HttpResponseOk<void>> => {
mutateAll(/@"\/private\/orders"@/);
- const res = request<void>(`${url}/private/orders/${orderId}/forget`, {
- method: "patch",
- token,
+ const res = request<void>(`/private/orders/${orderId}/forget`, {
+ method: "PATCH",
data,
});
// we may be forgetting some fields that are pare of the listing, so we must evict everything
@@ -138,9 +97,8 @@ export function useOrderAPI(): OrderAPI {
orderId: string,
): Promise<HttpResponseOk<void>> => {
mutateAll(/@"\/private\/orders"@/);
- const res = request<void>(`${url}/private/orders/${orderId}`, {
- method: "delete",
- token,
+ const res = request<void>(`/private/orders/${orderId}`, {
+ method: "DELETE",
});
await mutateAll(/.*private\/orders.*/);
return res;
@@ -150,10 +108,9 @@ export function useOrderAPI(): OrderAPI {
orderId: string,
): Promise<HttpResponseOk<string>> => {
return request<MerchantBackend.Orders.MerchantOrderStatusResponse>(
- `${url}/private/orders/${orderId}`,
+ `/private/orders/${orderId}`,
{
- method: "get",
- token,
+ method: "GET",
},
).then((res) => {
const url =
@@ -172,17 +129,12 @@ export function useOrderAPI(): OrderAPI {
export function useOrderDetails(
oderId: string,
): HttpResponse<MerchantBackend.Orders.MerchantOrderStatusResponse> {
- const { url: baseUrl, token: baseToken } = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
-
- const { url, token } = !admin
- ? { url: baseUrl, token: baseToken }
- : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+ const { fetcher } = useBackendInstanceRequest();
const { data, error, isValidating } = useSWR<
HttpResponseOk<MerchantBackend.Orders.MerchantOrderStatusResponse>,
HttpError
- >([`/private/orders/${oderId}`, token, url], fetcher, {
+ >([`/private/orders/${oderId}`], fetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
@@ -207,12 +159,7 @@ export function useInstanceOrders(
args?: InstanceOrderFilter,
updateFilter?: (d: Date) => void,
): HttpResponsePaginated<MerchantBackend.Orders.OrderHistory> {
- const { url: baseUrl, token: baseToken } = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
-
- const { url, token } = !admin
- ? { url: baseUrl, token: baseToken }
- : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+ const { orderFetcher } = useBackendInstanceRequest();
const [pageBefore, setPageBefore] = useState(1);
const [pageAfter, setPageAfter] = useState(1);
@@ -233,8 +180,6 @@ export function useInstanceOrders(
} = useSWR<HttpResponseOk<MerchantBackend.Orders.OrderHistory>, HttpError>(
[
`/private/orders`,
- token,
- url,
args?.paid,
args?.refunded,
args?.wired,
@@ -250,8 +195,6 @@ export function useInstanceOrders(
} = useSWR<HttpResponseOk<MerchantBackend.Orders.OrderHistory>, HttpError>(
[
`/private/orders`,
- token,
- url,
args?.paid,
args?.refunded,
args?.wired,
@@ -314,9 +257,9 @@ export function useInstanceOrders(
!beforeData || !afterData
? []
: (beforeData || lastBefore).data.orders
- .slice()
- .reverse()
- .concat((afterData || lastAfter).data.orders);
+ .slice()
+ .reverse()
+ .concat((afterData || lastAfter).data.orders);
if (loadingAfter || loadingBefore) return { loading: true, data: { orders } };
if (beforeData && afterData) {
return { ok: true, data: { orders }, ...pagination };
diff --git a/packages/merchant-backoffice-ui/src/hooks/product.test.ts b/packages/merchant-backoffice-ui/src/hooks/product.test.ts
new file mode 100644
index 000000000..a182b28f4
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/product.test.ts
@@ -0,0 +1,326 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { expect } from "chai";
+import { MerchantBackend } from "../declaration.js";
+import { useInstanceProducts, useProductAPI, useProductDetails } from "./product.js";
+import { ApiMockEnvironment } from "./testing.js";
+import {
+ API_CREATE_PRODUCT,
+ API_DELETE_PRODUCT, API_GET_PRODUCT_BY_ID,
+ API_LIST_PRODUCTS,
+ API_UPDATE_PRODUCT_BY_ID
+} from "./urls.js";
+
+describe("product api interaction with listing", () => {
+ it("should evict cache when creating a product", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_LIST_PRODUCTS, {
+ response: {
+ products: [{ product_id: "1234" }],
+ },
+ });
+ env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
+ response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
+ });
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const query = useInstanceProducts();
+ const api = useProductAPI();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(query.loading).undefined;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals([
+ { id: "1234", price: "ARS:12" },
+ ]);
+
+ env.addRequestExpectation(API_CREATE_PRODUCT, {
+ request: { price: "ARS:23" } as MerchantBackend.Products.ProductAddDetail,
+ });
+
+ env.addRequestExpectation(API_LIST_PRODUCTS, {
+ response: {
+ products: [{ product_id: "1234" }, { product_id: "2345" }],
+ },
+ });
+ env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
+ response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
+ });
+ env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
+ response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
+ });
+ env.addRequestExpectation(API_GET_PRODUCT_BY_ID("2345"), {
+ response: { price: "ARS:23" } as MerchantBackend.Products.ProductDetail,
+ });
+
+ api.createProduct({
+ price: "ARS:23",
+ } as any)
+
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).undefined;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals([
+ {
+ id: "1234",
+ price: "ARS:12",
+ },
+ {
+ id: "2345",
+ price: "ARS:23",
+ },
+ ]);
+ },
+ ], env.buildTestingContext());
+
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ });
+
+ it("should evict cache when updating a product", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_LIST_PRODUCTS, {
+ response: {
+ products: [{ product_id: "1234" }],
+ },
+ });
+ env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
+ response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
+ });
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const query = useInstanceProducts();
+ const api = useProductAPI();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).undefined;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals([
+ { id: "1234", price: "ARS:12" },
+ ]);
+
+ env.addRequestExpectation(API_UPDATE_PRODUCT_BY_ID("1234"), {
+ request: { price: "ARS:13" } as MerchantBackend.Products.ProductPatchDetail,
+ });
+
+ env.addRequestExpectation(API_LIST_PRODUCTS, {
+ response: {
+ products: [{ product_id: "1234" }],
+ },
+ });
+ env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
+ response: { price: "ARS:13" } as MerchantBackend.Products.ProductDetail,
+ });
+
+ api.updateProduct("1234", {
+ price: "ARS:13",
+ } as any)
+
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).undefined;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals([
+ {
+ id: "1234",
+ price: "ARS:13",
+ },
+ ]);
+ },
+ ], env.buildTestingContext());
+
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ });
+
+ it("should evict cache when deleting a product", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_LIST_PRODUCTS, {
+ response: {
+ products: [{ product_id: "1234" }, { product_id: "2345" }],
+ },
+ });
+ env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
+ response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
+ });
+ env.addRequestExpectation(API_GET_PRODUCT_BY_ID("2345"), {
+ response: { price: "ARS:23" } as MerchantBackend.Products.ProductDetail,
+ });
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const query = useInstanceProducts();
+ const api = useProductAPI();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(query.loading).undefined;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals([
+ { id: "1234", price: "ARS:12" },
+ { id: "2345", price: "ARS:23" },
+ ]);
+
+ env.addRequestExpectation(API_DELETE_PRODUCT("2345"), {});
+
+ env.addRequestExpectation(API_LIST_PRODUCTS, {
+ response: {
+ products: [{ product_id: "1234" }],
+ },
+ });
+
+ env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
+ response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
+ });
+ api.deleteProduct("2345");
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).undefined;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals([
+ { id: "1234", price: "ARS:12" },
+ ]);
+ },
+ ], env.buildTestingContext());
+
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ });
+
+});
+
+describe("product api interaction with details", () => {
+ it("should evict cache when updating a product", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_GET_PRODUCT_BY_ID("12"), {
+ response: {
+ description: "this is a description",
+ } as MerchantBackend.Products.ProductDetail,
+ });
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const query = useProductDetails("12");
+ const api = useProductAPI();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ description: "this is a description",
+ });
+
+ env.addRequestExpectation(API_UPDATE_PRODUCT_BY_ID("12"), {
+ request: { description: "other description" } as MerchantBackend.Products.ProductPatchDetail,
+ });
+
+ env.addRequestExpectation(API_GET_PRODUCT_BY_ID("12"), {
+ response: {
+ description: "other description",
+ } as MerchantBackend.Products.ProductDetail,
+ });
+
+ api.updateProduct("12", {
+ description: "other description",
+ } as any);
+
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ description: "other description",
+ });
+ },
+ ], env.buildTestingContext());
+
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ })
+}) \ No newline at end of file
diff --git a/packages/merchant-backoffice-ui/src/hooks/product.ts b/packages/merchant-backoffice-ui/src/hooks/product.ts
index fb7889834..af8ad74f3 100644
--- a/packages/merchant-backoffice-ui/src/hooks/product.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/product.ts
@@ -14,18 +14,9 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import useSWR, { useSWRConfig } from "swr";
-import { useBackendContext } from "../context/backend.js";
-import { useInstanceContext } from "../context/instance.js";
import { MerchantBackend, WithId } from "../declaration.js";
-import {
- fetcher,
- HttpError,
- HttpResponse,
- HttpResponseOk,
- multiFetcher,
- request,
- useMatchMutate,
-} from "./backend.js";
+import { HttpError, HttpResponse, HttpResponseOk } from "../utils/request.js";
+import { useBackendInstanceRequest, useMatchMutate } from "./backend.js";
export interface ProductAPI {
createProduct: (
@@ -45,19 +36,14 @@ export interface ProductAPI {
export function useProductAPI(): ProductAPI {
const mutateAll = useMatchMutate();
const { mutate } = useSWRConfig();
- const { url: baseUrl, token: adminToken } = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
- const { url, token } = !admin
- ? { url: baseUrl, token: adminToken }
- : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+ const { request } = useBackendInstanceRequest();
const createProduct = async (
data: MerchantBackend.Products.ProductAddDetail,
): Promise<void> => {
- const res = await request(`${url}/private/products`, {
- method: "post",
- token,
+ const res = await request(`/private/products`, {
+ method: "POST",
data,
});
@@ -68,9 +54,8 @@ export function useProductAPI(): ProductAPI {
productId: string,
data: MerchantBackend.Products.ProductPatchDetail,
): Promise<void> => {
- const r = await request(`${url}/private/products/${productId}`, {
- method: "patch",
- token,
+ const r = await request(`/private/products/${productId}`, {
+ method: "PATCH",
data,
});
@@ -78,20 +63,18 @@ export function useProductAPI(): ProductAPI {
};
const deleteProduct = async (productId: string): Promise<void> => {
- await request(`${url}/private/products/${productId}`, {
- method: "delete",
- token,
+ await request(`/private/products/${productId}`, {
+ method: "DELETE",
});
- await mutate([`/private/products`, token, url]);
+ await mutate([`/private/products`]);
};
const lockProduct = async (
productId: string,
data: MerchantBackend.Products.LockRequest,
): Promise<void> => {
- await request(`${url}/private/products/${productId}/lock`, {
- method: "post",
- token,
+ await request(`/private/products/${productId}/lock`, {
+ method: "POST",
data,
});
@@ -104,17 +87,12 @@ export function useProductAPI(): ProductAPI {
export function useInstanceProducts(): HttpResponse<
(MerchantBackend.Products.ProductDetail & WithId)[]
> {
- const { url: baseUrl, token: baseToken } = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
-
- const { url, token } = !admin
- ? { url: baseUrl, token: baseToken }
- : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+ const { fetcher, multiFetcher } = useBackendInstanceRequest();
const { data: list, error: listError } = useSWR<
HttpResponseOk<MerchantBackend.Products.InventorySummaryResponse>,
HttpError
- >([`/private/products`, token, url], fetcher, {
+ >([`/private/products`], fetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
@@ -128,7 +106,7 @@ export function useInstanceProducts(): HttpResponse<
const { data: products, error: productError } = useSWR<
HttpResponseOk<MerchantBackend.Products.ProductDetail>[],
HttpError
- >([paths, token, url], multiFetcher, {
+ >([paths], multiFetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
@@ -144,7 +122,7 @@ export function useInstanceProducts(): HttpResponse<
//take the id from the queried url
return {
...d.data,
- id: d.info?.url.replace(/.*\/private\/products\//, "") || "",
+ id: d.info?.url.href.replace(/.*\/private\/products\//, "") || "",
};
});
return { ok: true, data: dataWithId };
@@ -155,23 +133,12 @@ export function useInstanceProducts(): HttpResponse<
export function useProductDetails(
productId: string,
): HttpResponse<MerchantBackend.Products.ProductDetail> {
- const { url: baseUrl, token: baseToken } = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
-
- const { url, token } = !admin
- ? {
- url: baseUrl,
- token: baseToken,
- }
- : {
- url: `${baseUrl}/instances/${id}`,
- token: instanceToken,
- };
+ const { fetcher } = useBackendInstanceRequest();
const { data, error, isValidating } = useSWR<
HttpResponseOk<MerchantBackend.Products.ProductDetail>,
HttpError
- >([`/private/products/${productId}`, token, url], fetcher, {
+ >([`/private/products/${productId}`], fetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
diff --git a/packages/merchant-backoffice-ui/src/hooks/reserve.test.ts b/packages/merchant-backoffice-ui/src/hooks/reserve.test.ts
new file mode 100644
index 000000000..da0e054e5
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/reserve.test.ts
@@ -0,0 +1,431 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { expect } from "chai";
+import { MerchantBackend } from "../declaration.js";
+import {
+ useInstanceReserves,
+ useReserveDetails,
+ useReservesAPI,
+ useTipDetails
+} from "./reserves.js";
+import { ApiMockEnvironment } from "./testing.js";
+import {
+ API_AUTHORIZE_TIP,
+ API_AUTHORIZE_TIP_FOR_RESERVE,
+ API_CREATE_RESERVE,
+ API_DELETE_RESERVE,
+ API_GET_RESERVE_BY_ID,
+ API_GET_TIP_BY_ID,
+ API_LIST_RESERVES
+} from "./urls.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+
+describe("reserve api interaction with listing", () => {
+ it("should evict cache when creating a reserve", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_LIST_RESERVES, {
+ response: {
+ reserves: [
+ {
+ reserve_pub: "11",
+ } as MerchantBackend.Tips.ReserveStatusEntry,
+ ],
+ },
+ });
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const api = useReservesAPI();
+ const query = useInstanceReserves();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ reserves: [{ reserve_pub: "11" }],
+ });
+
+ env.addRequestExpectation(API_CREATE_RESERVE, {
+ request: {
+ initial_balance: "ARS:3333",
+ exchange_url: "http://url",
+ wire_method: "iban",
+ },
+ response: {
+ reserve_pub: "22",
+ payto_uri: "payto",
+ },
+ });
+
+ env.addRequestExpectation(API_LIST_RESERVES, {
+ response: {
+ reserves: [
+ {
+ reserve_pub: "11",
+ } as MerchantBackend.Tips.ReserveStatusEntry,
+ {
+ reserve_pub: "22",
+ } as MerchantBackend.Tips.ReserveStatusEntry,
+ ],
+ },
+ });
+
+ api.createReserve({
+ initial_balance: "ARS:3333",
+ exchange_url: "http://url",
+ wire_method: "iban",
+ })
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true;
+ if (!query.ok) return;
+
+ expect(query.data).deep.equals({
+ reserves: [
+ {
+ reserve_pub: "11",
+ } as MerchantBackend.Tips.ReserveStatusEntry,
+ {
+ reserve_pub: "22",
+ } as MerchantBackend.Tips.ReserveStatusEntry,
+ ],
+ });
+ },
+ ], env.buildTestingContext());
+
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ });
+
+ it("should evict cache when deleting a reserve", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_LIST_RESERVES, {
+ response: {
+ reserves: [
+ {
+ reserve_pub: "11",
+ } as MerchantBackend.Tips.ReserveStatusEntry,
+ {
+ reserve_pub: "22",
+ } as MerchantBackend.Tips.ReserveStatusEntry,
+ {
+ reserve_pub: "33",
+ } as MerchantBackend.Tips.ReserveStatusEntry,
+ ],
+ },
+ });
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const api = useReservesAPI();
+ const query = useInstanceReserves();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" })
+
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ reserves: [
+ { reserve_pub: "11" },
+ { reserve_pub: "22" },
+ { reserve_pub: "33" },
+ ],
+ });
+
+ env.addRequestExpectation(API_DELETE_RESERVE("11"), {});
+ env.addRequestExpectation(API_LIST_RESERVES, {
+ response: {
+ reserves: [
+ {
+ reserve_pub: "22",
+ } as MerchantBackend.Tips.ReserveStatusEntry,
+ {
+ reserve_pub: "33",
+ } as MerchantBackend.Tips.ReserveStatusEntry,
+ ],
+ },
+ });
+
+ api.deleteReserve("11")
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" })
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ reserves: [
+ { reserve_pub: "22" },
+ { reserve_pub: "33" },
+ ],
+ });
+ },
+ ], env.buildTestingContext());
+
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ });
+});
+
+describe("reserve api interaction with details", () => {
+ it("should evict cache when adding a tip for a specific reserve", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), {
+ response: {
+ payto_uri: "payto://here",
+ tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }],
+ } as MerchantBackend.Tips.ReserveDetail,
+ qparam: {
+ tips: "yes"
+ }
+ });
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const api = useReservesAPI();
+ const query = useReserveDetails("11");
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ payto_uri: "payto://here",
+ tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }],
+ });
+
+ env.addRequestExpectation(API_AUTHORIZE_TIP_FOR_RESERVE("11"), {
+ request: {
+ amount: "USD:12",
+ justification: "not",
+ next_url: "http://taler.net",
+ },
+ response: {
+ tip_id: "id2",
+ taler_tip_uri: "uri",
+ tip_expiration: { t_s: 1 },
+ tip_status_url: "url",
+ }
+ },);
+
+ env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), {
+ response: {
+ payto_uri: "payto://here",
+ tips: [
+ { reason: "why?", tip_id: "id1", total_amount: "USD:10" },
+ { reason: "not", tip_id: "id2", total_amount: "USD:12" },
+ ],
+ } as MerchantBackend.Tips.ReserveDetail,
+ qparam: {
+ tips: "yes"
+ }
+ });
+
+ api.authorizeTipReserve("11", {
+ amount: "USD:12",
+ justification: "not",
+ next_url: "http://taler.net",
+ })
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+
+ expect(query.loading).false;
+ expect(query.ok).true;
+ if (!query.ok) return;
+
+ expect(query.data).deep.equals({
+ payto_uri: "payto://here",
+ tips: [
+ { reason: "why?", tip_id: "id1", total_amount: "USD:10" },
+ { reason: "not", tip_id: "id2", total_amount: "USD:12" },
+ ],
+ });
+ },
+ ], env.buildTestingContext());
+
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ });
+
+ it("should evict cache when adding a tip for a random reserve", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), {
+ response: {
+ payto_uri: "payto://here",
+ tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }],
+ } as MerchantBackend.Tips.ReserveDetail,
+ qparam: {
+ tips: "yes"
+ }
+ });
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const api = useReservesAPI();
+ const query = useReserveDetails("11");
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ payto_uri: "payto://here",
+ tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }],
+ });
+
+ env.addRequestExpectation(API_AUTHORIZE_TIP, {
+ request: {
+ amount: "USD:12",
+ justification: "not",
+ next_url: "http://taler.net",
+ },
+ response: {
+ tip_id: "id2",
+ taler_tip_uri: "uri",
+ tip_expiration: { t_s: 1 },
+ tip_status_url: "url",
+ },
+ });
+
+ env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), {
+ response: {
+ payto_uri: "payto://here",
+ tips: [
+ { reason: "why?", tip_id: "id1", total_amount: "USD:10" },
+ { reason: "not", tip_id: "id2", total_amount: "USD:12" },
+ ],
+ } as MerchantBackend.Tips.ReserveDetail,
+ qparam: {
+ tips: "yes"
+ }
+ });
+
+ api.authorizeTip({
+ amount: "USD:12",
+ justification: "not",
+ next_url: "http://taler.net",
+ });
+
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).false;
+ expect(query.ok).true;
+ if (!query.ok) return;
+
+ expect(query.data).deep.equals({
+ payto_uri: "payto://here",
+ tips: [
+ { reason: "why?", tip_id: "id1", total_amount: "USD:10" },
+ { reason: "not", tip_id: "id2", total_amount: "USD:12" },
+ ],
+ });
+ },
+ ], env.buildTestingContext());
+
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ });
+});
+
+describe("reserve api interaction with tip details", () => {
+
+ it("should list tips", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_GET_TIP_BY_ID("11"), {
+ response: {
+ total_picked_up: "USD:12",
+ reason: "not",
+ } as MerchantBackend.Tips.TipDetails,
+ qparam: {
+ pickups: "yes"
+ }
+ });
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const query = useTipDetails("11");
+ return { query };
+ },
+ {},
+ [
+ ({ query }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(query.loading).true;
+ },
+ ({ query }) => {
+ expect(query.loading).false;
+ expect(query.ok).true
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ total_picked_up: "USD:12",
+ reason: "not",
+ });
+ },
+ ], env.buildTestingContext());
+
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+ });
+});
diff --git a/packages/merchant-backoffice-ui/src/hooks/reserves.ts b/packages/merchant-backoffice-ui/src/hooks/reserves.ts
index f6d77f113..dc127af13 100644
--- a/packages/merchant-backoffice-ui/src/hooks/reserves.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/reserves.ts
@@ -14,27 +14,14 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import useSWR, { useSWRConfig } from "swr";
-import { useBackendContext } from "../context/backend.js";
-import { useInstanceContext } from "../context/instance.js";
import { MerchantBackend } from "../declaration.js";
-import {
- fetcher,
- HttpError,
- HttpResponse,
- HttpResponseOk,
- request,
- useMatchMutate,
-} from "./backend.js";
+import { HttpError, HttpResponse, HttpResponseOk } from "../utils/request.js";
+import { useBackendInstanceRequest, useMatchMutate } from "./backend.js";
export function useReservesAPI(): ReserveMutateAPI {
const mutateAll = useMatchMutate();
const { mutate } = useSWRConfig();
- const { url: baseUrl, token: adminToken } = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
-
- const { url, token } = !admin
- ? { url: baseUrl, token: adminToken }
- : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+ const { request } = useBackendInstanceRequest();
const createReserve = async (
data: MerchantBackend.Tips.ReserveCreateRequest,
@@ -42,10 +29,9 @@ export function useReservesAPI(): ReserveMutateAPI {
HttpResponseOk<MerchantBackend.Tips.ReserveCreateConfirmation>
> => {
const res = await request<MerchantBackend.Tips.ReserveCreateConfirmation>(
- `${url}/private/reserves`,
+ `/private/reserves`,
{
- method: "post",
- token,
+ method: "POST",
data,
},
);
@@ -61,16 +47,15 @@ export function useReservesAPI(): ReserveMutateAPI {
data: MerchantBackend.Tips.TipCreateRequest,
): Promise<HttpResponseOk<MerchantBackend.Tips.TipCreateConfirmation>> => {
const res = await request<MerchantBackend.Tips.TipCreateConfirmation>(
- `${url}/private/reserves/${pub}/authorize-tip`,
+ `/private/reserves/${pub}/authorize-tip`,
{
- method: "post",
- token,
+ method: "POST",
data,
},
);
//evict reserve details query
- await mutate([`/private/reserves/${pub}`, token, url]);
+ await mutate([`/private/reserves/${pub}`]);
return res;
};
@@ -79,10 +64,9 @@ export function useReservesAPI(): ReserveMutateAPI {
data: MerchantBackend.Tips.TipCreateRequest,
): Promise<HttpResponseOk<MerchantBackend.Tips.TipCreateConfirmation>> => {
const res = await request<MerchantBackend.Tips.TipCreateConfirmation>(
- `${url}/private/tips`,
+ `/private/tips`,
{
- method: "post",
- token,
+ method: "POST",
data,
},
);
@@ -94,9 +78,8 @@ export function useReservesAPI(): ReserveMutateAPI {
};
const deleteReserve = async (pub: string): Promise<HttpResponse<void>> => {
- const res = await request<void>(`${url}/private/reserves/${pub}`, {
- method: "delete",
- token,
+ const res = await request<void>(`/private/reserves/${pub}`, {
+ method: "DELETE",
});
//evict reserve list query
@@ -123,17 +106,12 @@ export interface ReserveMutateAPI {
}
export function useInstanceReserves(): HttpResponse<MerchantBackend.Tips.TippingReserveStatus> {
- const { url: baseUrl, token: baseToken } = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
-
- const { url, token } = !admin
- ? { url: baseUrl, token: baseToken }
- : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+ const { fetcher } = useBackendInstanceRequest();
const { data, error, isValidating } = useSWR<
HttpResponseOk<MerchantBackend.Tips.TippingReserveStatus>,
HttpError
- >([`/private/reserves`, token, url], fetcher);
+ >([`/private/reserves`], fetcher);
if (isValidating) return { loading: true, data: data?.data };
if (data) return data;
@@ -144,15 +122,12 @@ export function useInstanceReserves(): HttpResponse<MerchantBackend.Tips.Tipping
export function useReserveDetails(
reserveId: string,
): HttpResponse<MerchantBackend.Tips.ReserveDetail> {
- const { url: baseUrl } = useBackendContext();
- const { token, id: instanceId, admin } = useInstanceContext();
-
- const url = !admin ? baseUrl : `${baseUrl}/instances/${instanceId}`;
+ const { reserveDetailFetcher } = useBackendInstanceRequest();
const { data, error, isValidating } = useSWR<
HttpResponseOk<MerchantBackend.Tips.ReserveDetail>,
HttpError
- >([`/private/reserves/${reserveId}`, token, url], reserveDetailFetcher, {
+ >([`/private/reserves/${reserveId}`], reserveDetailFetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
@@ -169,15 +144,12 @@ export function useReserveDetails(
export function useTipDetails(
tipId: string,
): HttpResponse<MerchantBackend.Tips.TipDetails> {
- const { url: baseUrl } = useBackendContext();
- const { token, id: instanceId, admin } = useInstanceContext();
-
- const url = !admin ? baseUrl : `${baseUrl}/instances/${instanceId}`;
+ const { tipsDetailFetcher } = useBackendInstanceRequest();
const { data, error, isValidating } = useSWR<
HttpResponseOk<MerchantBackend.Tips.TipDetails>,
HttpError
- >([`/private/tips/${tipId}`, token, url], tipsDetailFetcher, {
+ >([`/private/tips/${tipId}`], tipsDetailFetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
@@ -190,29 +162,3 @@ export function useTipDetails(
if (error) return error;
return { loading: true };
}
-
-function reserveDetailFetcher<T>(
- url: string,
- token: string,
- backend: string,
-): Promise<HttpResponseOk<T>> {
- return request<T>(`${backend}${url}`, {
- token,
- params: {
- tips: "yes",
- },
- });
-}
-
-function tipsDetailFetcher<T>(
- url: string,
- token: string,
- backend: string,
-): Promise<HttpResponseOk<T>> {
- return request<T>(`${backend}${url}`, {
- token,
- params: {
- pickups: "yes",
- },
- });
-}
diff --git a/packages/merchant-backoffice-ui/src/hooks/templates.ts b/packages/merchant-backoffice-ui/src/hooks/templates.ts
index 3e69d78d0..55c3875b5 100644
--- a/packages/merchant-backoffice-ui/src/hooks/templates.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/templates.ts
@@ -14,57 +14,26 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { MerchantBackend } from "../declaration.js";
-import { useBackendContext } from "../context/backend.js";
+import { useMatchMutate, useBackendInstanceRequest } from "./backend.js";
+import useSWR from "swr";
+import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js";
+import { useEffect, useState } from "preact/hooks";
import {
- request,
- HttpResponse,
HttpError,
+ HttpResponse,
HttpResponseOk,
HttpResponsePaginated,
- useMatchMutate,
-} from "./backend.js";
-import useSWR from "swr";
-import { useInstanceContext } from "../context/instance.js";
-import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js";
-import { useEffect, useState } from "preact/hooks";
-
-async function templateFetcher<T>(
- url: string,
- token: string,
- backend: string,
- position?: string,
- delta?: number,
-): Promise<HttpResponseOk<T>> {
- const params: any = {};
- if (delta !== undefined) {
- params.limit = delta;
- }
- if (position !== undefined) params.offset = position;
-
- return request<T>(`${backend}${url}`, { token, params });
-}
+} from "../utils/request.js";
export function useTemplateAPI(): TemplateAPI {
const mutateAll = useMatchMutate();
- const { url: baseUrl, token: adminToken } = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
-
- const { url, token } = !admin
- ? {
- url: baseUrl,
- token: adminToken,
- }
- : {
- url: `${baseUrl}/instances/${id}`,
- token: instanceToken,
- };
+ const { request } = useBackendInstanceRequest();
const createTemplate = async (
data: MerchantBackend.Template.TemplateAddDetails,
): Promise<HttpResponseOk<void>> => {
- const res = await request<void>(`${url}/private/templates`, {
- method: "post",
- token,
+ const res = await request<void>(`/private/templates`, {
+ method: "POST",
data,
});
await mutateAll(/.*private\/templates.*/);
@@ -75,9 +44,8 @@ export function useTemplateAPI(): TemplateAPI {
templateId: string,
data: MerchantBackend.Template.TemplatePatchDetails,
): Promise<HttpResponseOk<void>> => {
- const res = await request<void>(`${url}/private/templates/${templateId}`, {
- method: "patch",
- token,
+ const res = await request<void>(`/private/templates/${templateId}`, {
+ method: "PATCH",
data,
});
await mutateAll(/.*private\/templates.*/);
@@ -87,9 +55,8 @@ export function useTemplateAPI(): TemplateAPI {
const deleteTemplate = async (
templateId: string,
): Promise<HttpResponseOk<void>> => {
- const res = await request<void>(`${url}/private/templates/${templateId}`, {
- method: "delete",
- token,
+ const res = await request<void>(`/private/templates/${templateId}`, {
+ method: "DELETE",
});
await mutateAll(/.*private\/templates.*/);
return res;
@@ -102,10 +69,9 @@ export function useTemplateAPI(): TemplateAPI {
HttpResponseOk<MerchantBackend.Template.UsingTemplateResponse>
> => {
const res = await request<MerchantBackend.Template.UsingTemplateResponse>(
- `${url}/private/templates/${templateId}`,
+ `/private/templates/${templateId}`,
{
- method: "post",
- token,
+ method: "POST",
data,
},
);
@@ -140,12 +106,7 @@ export function useInstanceTemplates(
args?: InstanceTemplateFilter,
updatePosition?: (id: string) => void,
): HttpResponsePaginated<MerchantBackend.Template.TemplateSummaryResponse> {
- const { url: baseUrl, token: baseToken } = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
-
- const { url, token } = !admin
- ? { url: baseUrl, token: baseToken }
- : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+ const { templateFetcher } = useBackendInstanceRequest();
// const [pageBefore, setPageBefore] = useState(1);
const [pageAfter, setPageAfter] = useState(1);
@@ -180,10 +141,7 @@ export function useInstanceTemplates(
} = useSWR<
HttpResponseOk<MerchantBackend.Template.TemplateSummaryResponse>,
HttpError
- >(
- [`/private/templates`, token, url, args?.position, -totalAfter],
- templateFetcher,
- );
+ >([`/private/templates`, args?.position, -totalAfter], templateFetcher);
//this will save last result
// const [lastBefore, setLastBefore] = useState<
@@ -216,10 +174,9 @@ export function useInstanceTemplates(
if (afterData.data.templates.length < MAX_RESULT_SIZE) {
setPageAfter(pageAfter + 1);
} else {
- const from = `${
- afterData.data.templates[afterData.data.templates.length - 1]
- .template_id
- }`;
+ const from = `${afterData.data.templates[afterData.data.templates.length - 1]
+ .template_id
+ }`;
if (from && updatePosition) updatePosition(from);
}
},
@@ -255,17 +212,12 @@ export function useInstanceTemplates(
export function useTemplateDetails(
templateId: string,
): HttpResponse<MerchantBackend.Template.TemplateDetails> {
- const { url: baseUrl, token: baseToken } = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
-
- const { url, token } = !admin
- ? { url: baseUrl, token: baseToken }
- : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+ const { templateFetcher } = useBackendInstanceRequest();
const { data, error, isValidating } = useSWR<
HttpResponseOk<MerchantBackend.Template.TemplateDetails>,
HttpError
- >([`/private/templates/${templateId}`, token, url], templateFetcher, {
+ >([`/private/templates/${templateId}`], templateFetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
diff --git a/packages/merchant-backoffice-ui/src/hooks/testing.tsx b/packages/merchant-backoffice-ui/src/hooks/testing.tsx
new file mode 100644
index 000000000..8c5a5a36b
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/testing.tsx
@@ -0,0 +1,120 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { MockEnvironment } from "@gnu-taler/web-util/lib/tests/mock";
+import { ComponentChildren, FunctionalComponent, h, VNode } from "preact";
+import { SWRConfig } from "swr";
+import { ApiContextProvider } from "../context/api.js";
+import { BackendContextProvider } from "../context/backend.js";
+import { InstanceContextProvider } from "../context/instance.js";
+import { HttpResponseOk, RequestOptions } from "../utils/request.js";
+
+export class ApiMockEnvironment extends MockEnvironment {
+ constructor(debug = false) {
+ super(debug);
+ }
+
+ mockApiIfNeeded(): void {
+ null; // do nothing
+ }
+
+ public buildTestingContext(): FunctionalComponent<{
+ children: ComponentChildren;
+ }> {
+ const __SAVE_REQUEST_AND_GET_MOCKED_RESPONSE =
+ this.saveRequestAndGetMockedResponse.bind(this);
+
+ return function TestingContext({
+ children,
+ }: {
+ children: ComponentChildren;
+ }): VNode {
+ async function request<T>(
+ base: string,
+ path: string,
+ options: RequestOptions = {},
+ ): Promise<HttpResponseOk<T>> {
+ const _url = new URL(`${base}${path}`);
+ // Object.entries(options.params ?? {}).forEach(([key, value]) => {
+ // _url.searchParams.set(key, String(value));
+ // });
+
+ const mocked = __SAVE_REQUEST_AND_GET_MOCKED_RESPONSE(
+ {
+ method: options.method ?? "GET",
+ url: _url.href,
+ },
+ {
+ qparam: options.params,
+ auth: options.token,
+ request: options.data,
+ },
+ );
+
+ return {
+ ok: true,
+ data: (!mocked ? undefined : mocked.payload) as T,
+ loading: false,
+ clientError: false,
+ serverError: false,
+ info: {
+ hasToken: !!options.token,
+ status: !mocked ? 200 : mocked.status,
+ url: _url,
+ payload: options.data,
+ },
+ };
+ }
+ const SC: any = SWRConfig;
+
+ return (
+ <BackendContextProvider
+ defaultUrl="http://backend"
+ initialToken={undefined}
+ >
+ <InstanceContextProvider
+ value={{
+ token: undefined,
+ id: "default",
+ admin: true,
+ changeToken: () => null,
+ }}
+ >
+ <ApiContextProvider value={{ request }}>
+ <SC
+ value={{
+ loadingTimeout: 0,
+ dedupingInterval: 0,
+ shouldRetryOnError: false,
+ errorRetryInterval: 0,
+ errorRetryCount: 0,
+ provider: () => new Map(),
+ }}
+ >
+ {children}
+ </SC>
+ </ApiContextProvider>
+ </InstanceContextProvider>
+ </BackendContextProvider>
+ );
+ };
+ }
+}
diff --git a/packages/merchant-backoffice-ui/src/hooks/transfer.test.ts b/packages/merchant-backoffice-ui/src/hooks/transfer.test.ts
new file mode 100644
index 000000000..a553ed362
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/transfer.test.ts
@@ -0,0 +1,277 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { expect } from "chai";
+import { MerchantBackend } from "../declaration.js";
+import { API_INFORM_TRANSFERS, API_LIST_TRANSFERS } from "./urls.js";
+import { ApiMockEnvironment } from "./testing.js";
+import { useInstanceTransfers, useTransferAPI } from "./transfer.js";
+
+describe("transfer api interaction with listing", () => {
+ it("should evict cache when informing a transfer", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_LIST_TRANSFERS, {
+ qparam: { limit: 0 },
+ response: {
+ transfers: [{ wtid: "2" } as MerchantBackend.Transfers.TransferDetails],
+ },
+ });
+ // FIXME: is this query really needed? if the hook is rendered without
+ // position argument then then backend is returning the newest and no need
+ // to this second query
+ env.addRequestExpectation(API_LIST_TRANSFERS, {
+ qparam: { limit: -20 },
+ response: {
+ transfers: [],
+ },
+ });
+
+ const moveCursor = (d: string) => {
+ console.log("new position", d);
+ };
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ const query = useInstanceTransfers({}, moveCursor);
+ const api = useTransferAPI();
+ return { query, api };
+ },
+ {},
+ [
+ ({ query, api }) => {
+ expect(query.loading).true;
+ },
+
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({
+ result: "ok",
+ });
+ expect(query.loading).undefined;
+ expect(query.ok).true;
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ transfers: [{ wtid: "2" }],
+ });
+
+ env.addRequestExpectation(API_INFORM_TRANSFERS, {
+ request: {
+ wtid: "3",
+ credit_amount: "EUR:1",
+ exchange_url: "exchange.url",
+ payto_uri: "payto://",
+ },
+ response: { total: "" } as any,
+ });
+
+ env.addRequestExpectation(API_LIST_TRANSFERS, {
+ qparam: { limit: 0 },
+ response: {
+ transfers: [{ wtid: "2" } as any, { wtid: "3" } as any],
+ },
+ });
+
+ env.addRequestExpectation(API_LIST_TRANSFERS, {
+ qparam: { limit: -20 },
+ response: {
+ transfers: [],
+ },
+ });
+
+ api.informTransfer({
+ wtid: "3",
+ credit_amount: "EUR:1",
+ exchange_url: "exchange.url",
+ payto_uri: "payto://",
+ });
+ },
+ ({ query, api }) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({
+ result: "ok",
+ });
+ expect(query.loading).undefined;
+ expect(query.ok).true;
+ if (!query.ok) return;
+
+ expect(query.data).deep.equals({
+ transfers: [{ wtid: "3" }, { wtid: "2" }],
+ });
+ },
+ ],
+ env.buildTestingContext(),
+ );
+
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ });
+});
+
+describe("transfer listing pagination", () => {
+ it("should not load more if has reach the end", async () => {
+ const env = new ApiMockEnvironment();
+
+ env.addRequestExpectation(API_LIST_TRANSFERS, {
+ qparam: { limit: 0, payto_uri: "payto://" },
+ response: {
+ transfers: [{ wtid: "2" } as any],
+ },
+ });
+
+ env.addRequestExpectation(API_LIST_TRANSFERS, {
+ qparam: { limit: -20, payto_uri: "payto://" },
+ response: {
+ transfers: [{ wtid: "1" } as any],
+ },
+ });
+
+ const moveCursor = (d: string) => {
+ console.log("new position", d);
+ };
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ return useInstanceTransfers({ payto_uri: "payto://" }, moveCursor);
+ },
+ {},
+ [
+ (query) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({
+ result: "ok",
+ });
+ expect(query.loading).true;
+ },
+ (query) => {
+ expect(query.loading).undefined;
+ expect(query.ok).true;
+ if (!query.ok) return;
+ expect(query.data).deep.equals({
+ transfers: [{ wtid: "2" }, { wtid: "1" }],
+ });
+ expect(query.isReachingEnd).true;
+ expect(query.isReachingStart).true;
+
+ //check that this button won't trigger more updates since
+ //has reach end and start
+ query.loadMore();
+ query.loadMorePrev();
+ },
+ ],
+ env.buildTestingContext(),
+ );
+
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ });
+
+ it("should load more if result brings more that PAGE_SIZE", async () => {
+ const env = new ApiMockEnvironment();
+
+ const transfersFrom0to20 = Array.from({ length: 20 }).map((e, i) => ({
+ wtid: String(i),
+ }));
+ const transfersFrom20to40 = Array.from({ length: 20 }).map((e, i) => ({
+ wtid: String(i + 20),
+ }));
+ const transfersFrom20to0 = [...transfersFrom0to20].reverse();
+
+ env.addRequestExpectation(API_LIST_TRANSFERS, {
+ qparam: { limit: 20, payto_uri: "payto://", offset: "1" },
+ response: {
+ transfers: transfersFrom0to20,
+ },
+ });
+
+ env.addRequestExpectation(API_LIST_TRANSFERS, {
+ qparam: { limit: -20, payto_uri: "payto://", offset: "1" },
+ response: {
+ transfers: transfersFrom20to40,
+ },
+ });
+
+ const moveCursor = (d: string) => {
+ console.log("new position", d);
+ };
+
+ const hookBehavior = await tests.hookBehaveLikeThis(
+ () => {
+ return useInstanceTransfers(
+ { payto_uri: "payto://", position: "1" },
+ moveCursor,
+ );
+ },
+ {},
+ [
+ (result) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({
+ result: "ok",
+ });
+ expect(result.loading).true;
+ },
+ (result) => {
+ expect(result.loading).undefined;
+ expect(result.ok).true;
+ if (!result.ok) return;
+ expect(result.data).deep.equals({
+ transfers: [...transfersFrom20to0, ...transfersFrom20to40],
+ });
+ expect(result.isReachingEnd).false;
+ expect(result.isReachingStart).false;
+
+ //query more
+ env.addRequestExpectation(API_LIST_TRANSFERS, {
+ qparam: { limit: -40, payto_uri: "payto://", offset: "1" },
+ response: {
+ transfers: [...transfersFrom20to40, { wtid: "41" }],
+ },
+ });
+ result.loadMore();
+ },
+ (result) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({
+ result: "ok",
+ });
+ expect(result.loading).true;
+ },
+ (result) => {
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({
+ result: "ok",
+ });
+ expect(result.loading).undefined;
+ expect(result.ok).true;
+ if (!result.ok) return;
+ expect(result.data).deep.equals({
+ transfers: [
+ ...transfersFrom20to0,
+ ...transfersFrom20to40,
+ { wtid: "41" },
+ ],
+ });
+ expect(result.isReachingEnd).true;
+ expect(result.isReachingStart).false;
+ },
+ ],
+ env.buildTestingContext(),
+ );
+
+ expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+ expect(hookBehavior).deep.eq({ result: "ok" });
+ });
+});
diff --git a/packages/merchant-backoffice-ui/src/hooks/transfer.ts b/packages/merchant-backoffice-ui/src/hooks/transfer.ts
index d1ac2c285..c827772e4 100644
--- a/packages/merchant-backoffice-ui/src/hooks/transfer.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/transfer.ts
@@ -13,55 +13,21 @@
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 { useEffect, useState } from "preact/hooks";
+import useSWR from "swr";
import { MerchantBackend } from "../declaration.js";
-import { useBackendContext } from "../context/backend.js";
+import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js";
import {
- request,
- HttpResponse,
HttpError,
+ HttpResponse,
HttpResponseOk,
HttpResponsePaginated,
- useMatchMutate,
-} from "./backend.js";
-import useSWR from "swr";
-import { useInstanceContext } from "../context/instance.js";
-import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js";
-import { useEffect, useState } from "preact/hooks";
-
-async function transferFetcher<T>(
- url: string,
- token: string,
- backend: string,
- payto_uri?: string,
- verified?: string,
- position?: string,
- delta?: number,
-): Promise<HttpResponseOk<T>> {
- const params: any = {};
- if (payto_uri !== undefined) params.payto_uri = payto_uri;
- if (verified !== undefined) params.verified = verified;
- if (delta !== undefined) {
- params.limit = delta;
- }
- if (position !== undefined) params.offset = position;
-
- return request<T>(`${backend}${url}`, { token, params });
-}
+} from "../utils/request.js";
+import { useBackendInstanceRequest, useMatchMutate } from "./backend.js";
export function useTransferAPI(): TransferAPI {
const mutateAll = useMatchMutate();
- const { url: baseUrl, token: adminToken } = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
-
- const { url, token } = !admin
- ? {
- url: baseUrl,
- token: adminToken,
- }
- : {
- url: `${baseUrl}/instances/${id}`,
- token: instanceToken,
- };
+ const { request } = useBackendInstanceRequest();
const informTransfer = async (
data: MerchantBackend.Transfers.TransferInformation,
@@ -70,10 +36,9 @@ export function useTransferAPI(): TransferAPI {
> => {
const res =
await request<MerchantBackend.Transfers.MerchantTrackTransferResponse>(
- `${url}/private/transfers`,
+ `/private/transfers`,
{
- method: "post",
- token,
+ method: "POST",
data,
},
);
@@ -103,12 +68,7 @@ export function useInstanceTransfers(
args?: InstanceTransferFilter,
updatePosition?: (id: string) => void,
): HttpResponsePaginated<MerchantBackend.Transfers.TransferList> {
- const { url: baseUrl, token: baseToken } = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
-
- const { url, token } = !admin
- ? { url: baseUrl, token: baseToken }
- : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+ const { transferFetcher } = useBackendInstanceRequest();
const [pageBefore, setPageBefore] = useState(1);
const [pageAfter, setPageAfter] = useState(1);
@@ -129,8 +89,6 @@ export function useInstanceTransfers(
} = useSWR<HttpResponseOk<MerchantBackend.Transfers.TransferList>, HttpError>(
[
`/private/transfers`,
- token,
- url,
args?.payto_uri,
args?.verified,
args?.position,
@@ -145,8 +103,6 @@ export function useInstanceTransfers(
} = useSWR<HttpResponseOk<MerchantBackend.Transfers.TransferList>, HttpError>(
[
`/private/transfers`,
- token,
- url,
args?.payto_uri,
args?.verified,
args?.position,
@@ -185,10 +141,9 @@ export function useInstanceTransfers(
if (afterData.data.transfers.length < MAX_RESULT_SIZE) {
setPageAfter(pageAfter + 1);
} else {
- const from = `${
- afterData.data.transfers[afterData.data.transfers.length - 1]
+ const from = `${afterData.data.transfers[afterData.data.transfers.length - 1]
.transfer_serial_id
- }`;
+ }`;
if (from && updatePosition) updatePosition(from);
}
},
@@ -197,10 +152,9 @@ export function useInstanceTransfers(
if (beforeData.data.transfers.length < MAX_RESULT_SIZE) {
setPageBefore(pageBefore + 1);
} else if (beforeData) {
- const from = `${
- beforeData.data.transfers[beforeData.data.transfers.length - 1]
+ const from = `${beforeData.data.transfers[beforeData.data.transfers.length - 1]
.transfer_serial_id
- }`;
+ }`;
if (from && updatePosition) updatePosition(from);
}
},
@@ -210,9 +164,9 @@ export function useInstanceTransfers(
!beforeData || !afterData
? []
: (beforeData || lastBefore).data.transfers
- .slice()
- .reverse()
- .concat((afterData || lastAfter).data.transfers);
+ .slice()
+ .reverse()
+ .concat((afterData || lastAfter).data.transfers);
if (loadingAfter || loadingBefore)
return { loading: true, data: { transfers } };
if (beforeData && afterData) {
diff --git a/packages/merchant-backoffice-ui/src/hooks/urls.ts b/packages/merchant-backoffice-ui/src/hooks/urls.ts
new file mode 100644
index 000000000..05494c0c9
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/urls.ts
@@ -0,0 +1,291 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+import { Query } from "@gnu-taler/web-util/lib/tests/mock";
+import { MerchantBackend } from "../declaration.js";
+
+////////////////////
+// ORDER
+////////////////////
+
+export const API_CREATE_ORDER: Query<
+ MerchantBackend.Orders.PostOrderRequest,
+ MerchantBackend.Orders.PostOrderResponse
+> = {
+ method: "POST",
+ url: "http://backend/instances/default/private/orders",
+};
+
+export const API_GET_ORDER_BY_ID = (
+ id: string,
+): Query<unknown, MerchantBackend.Orders.MerchantOrderStatusResponse> => ({
+ method: "GET",
+ url: `http://backend/instances/default/private/orders/${id}`,
+});
+
+export const API_LIST_ORDERS: Query<
+ unknown,
+ MerchantBackend.Orders.OrderHistory
+> = {
+ method: "GET",
+ url: "http://backend/instances/default/private/orders",
+};
+
+export const API_REFUND_ORDER_BY_ID = (
+ id: string,
+): Query<
+ MerchantBackend.Orders.RefundRequest,
+ MerchantBackend.Orders.MerchantRefundResponse
+> => ({
+ method: "POST",
+ url: `http://backend/instances/default/private/orders/${id}/refund`,
+});
+
+export const API_FORGET_ORDER_BY_ID = (
+ id: string,
+): Query<MerchantBackend.Orders.ForgetRequest, unknown> => ({
+ method: "PATCH",
+ url: `http://backend/instances/default/private/orders/${id}/forget`,
+});
+
+export const API_DELETE_ORDER = (
+ id: string,
+): Query<MerchantBackend.Orders.ForgetRequest, unknown> => ({
+ method: "DELETE",
+ url: `http://backend/instances/default/private/orders/${id}`,
+});
+
+////////////////////
+// TRANSFER
+////////////////////
+
+export const API_LIST_TRANSFERS: Query<
+ unknown,
+ MerchantBackend.Transfers.TransferList
+> = {
+ method: "GET",
+ url: "http://backend/instances/default/private/transfers",
+};
+
+export const API_INFORM_TRANSFERS: Query<
+ MerchantBackend.Transfers.TransferInformation,
+ MerchantBackend.Transfers.MerchantTrackTransferResponse
+> = {
+ method: "POST",
+ url: "http://backend/instances/default/private/transfers",
+};
+
+////////////////////
+// PRODUCT
+////////////////////
+
+export const API_CREATE_PRODUCT: Query<
+ MerchantBackend.Products.ProductAddDetail,
+ unknown
+> = {
+ method: "POST",
+ url: "http://backend/instances/default/private/products",
+};
+
+export const API_LIST_PRODUCTS: Query<
+ unknown,
+ MerchantBackend.Products.InventorySummaryResponse
+> = {
+ method: "GET",
+ url: "http://backend/instances/default/private/products",
+};
+
+export const API_GET_PRODUCT_BY_ID = (
+ id: string,
+): Query<unknown, MerchantBackend.Products.ProductDetail> => ({
+ method: "GET",
+ url: `http://backend/instances/default/private/products/${id}`,
+});
+
+export const API_UPDATE_PRODUCT_BY_ID = (
+ id: string,
+): Query<
+ MerchantBackend.Products.ProductPatchDetail,
+ MerchantBackend.Products.InventorySummaryResponse
+> => ({
+ method: "PATCH",
+ url: `http://backend/instances/default/private/products/${id}`,
+});
+
+export const API_DELETE_PRODUCT = (id: string): Query<unknown, unknown> => ({
+ method: "DELETE",
+ url: `http://backend/instances/default/private/products/${id}`,
+});
+
+////////////////////
+// RESERVES
+////////////////////
+
+export const API_CREATE_RESERVE: Query<
+ MerchantBackend.Tips.ReserveCreateRequest,
+ MerchantBackend.Tips.ReserveCreateConfirmation
+> = {
+ method: "POST",
+ url: "http://backend/instances/default/private/reserves",
+};
+export const API_LIST_RESERVES: Query<
+ unknown,
+ MerchantBackend.Tips.TippingReserveStatus
+> = {
+ method: "GET",
+ url: "http://backend/instances/default/private/reserves",
+};
+
+export const API_GET_RESERVE_BY_ID = (
+ pub: string,
+): Query<unknown, MerchantBackend.Tips.ReserveDetail> => ({
+ method: "GET",
+ url: `http://backend/instances/default/private/reserves/${pub}`,
+});
+
+export const API_GET_TIP_BY_ID = (
+ pub: string,
+): Query<unknown, MerchantBackend.Tips.TipDetails> => ({
+ method: "GET",
+ url: `http://backend/instances/default/private/tips/${pub}`,
+});
+
+export const API_AUTHORIZE_TIP_FOR_RESERVE = (
+ pub: string,
+): Query<
+ MerchantBackend.Tips.TipCreateRequest,
+ MerchantBackend.Tips.TipCreateConfirmation
+> => ({
+ method: "POST",
+ url: `http://backend/instances/default/private/reserves/${pub}/authorize-tip`,
+});
+
+export const API_AUTHORIZE_TIP: Query<
+ MerchantBackend.Tips.TipCreateRequest,
+ MerchantBackend.Tips.TipCreateConfirmation
+> = {
+ method: "POST",
+ url: `http://backend/instances/default/private/tips`,
+};
+
+export const API_DELETE_RESERVE = (id: string): Query<unknown, unknown> => ({
+ method: "DELETE",
+ url: `http://backend/instances/default/private/reserves/${id}`,
+});
+
+////////////////////
+// INSTANCE ADMIN
+////////////////////
+
+export const API_CREATE_INSTANCE: Query<
+ MerchantBackend.Instances.InstanceConfigurationMessage,
+ unknown
+> = {
+ method: "POST",
+ url: "http://backend/management/instances",
+};
+
+export const API_GET_INSTANCE_BY_ID = (
+ id: string,
+): Query<unknown, MerchantBackend.Instances.QueryInstancesResponse> => ({
+ method: "GET",
+ url: `http://backend/management/instances/${id}`,
+});
+
+export const API_GET_INSTANCE_KYC_BY_ID = (
+ id: string,
+): Query<unknown, MerchantBackend.Instances.AccountKycRedirects> => ({
+ method: "GET",
+ url: `http://backend/management/instances/${id}/kyc`,
+});
+
+export const API_LIST_INSTANCES: Query<
+ unknown,
+ MerchantBackend.Instances.InstancesResponse
+> = {
+ method: "GET",
+ url: "http://backend/management/instances",
+};
+
+export const API_UPDATE_INSTANCE_BY_ID = (
+ id: string,
+): Query<
+ MerchantBackend.Instances.InstanceReconfigurationMessage,
+ unknown
+> => ({
+ method: "PATCH",
+ url: `http://backend/management/instances/${id}`,
+});
+
+export const API_UPDATE_INSTANCE_AUTH_BY_ID = (
+ id: string,
+): Query<
+ MerchantBackend.Instances.InstanceAuthConfigurationMessage,
+ unknown
+> => ({
+ method: "POST",
+ url: `http://backend/management/instances/${id}/auth`,
+});
+
+export const API_DELETE_INSTANCE = (id: string): Query<unknown, unknown> => ({
+ method: "DELETE",
+ url: `http://backend/management/instances/${id}`,
+});
+
+////////////////////
+// INSTANCE
+////////////////////
+
+export const API_GET_CURRENT_INSTANCE: Query<
+ unknown,
+ MerchantBackend.Instances.QueryInstancesResponse
+> = {
+ method: "GET",
+ url: `http://backend/instances/default/private/`,
+};
+
+export const API_GET_CURRENT_INSTANCE_KYC: Query<
+ unknown,
+ MerchantBackend.Instances.AccountKycRedirects
+> = {
+ method: "GET",
+ url: `http://backend/instances/default/private/kyc`,
+};
+
+export const API_UPDATE_CURRENT_INSTANCE: Query<
+ MerchantBackend.Instances.InstanceReconfigurationMessage,
+ unknown
+> = {
+ method: "PATCH",
+ url: `http://backend/instances/default/private/`,
+};
+
+export const API_UPDATE_CURRENT_INSTANCE_AUTH: Query<
+ MerchantBackend.Instances.InstanceAuthConfigurationMessage,
+ unknown
+> = {
+ method: "POST",
+ url: `http://backend/instances/default/private/auth`,
+};
+
+export const API_DELETE_CURRENT_INSTANCE: Query<unknown, unknown> = {
+ method: "DELETE",
+ url: `http://backend/instances/default/private`,
+};
diff --git a/packages/merchant-backoffice-ui/src/manifest.json b/packages/merchant-backoffice-ui/src/manifest.json
deleted file mode 100644
index 2c3de2339..000000000
--- a/packages/merchant-backoffice-ui/src/manifest.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "name": "backoffice-preact",
- "short_name": "backoffice-preact",
- "start_url": "/",
- "display": "standalone",
- "orientation": "portrait",
- "background_color": "#fff",
- "theme_color": "#673ab8",
- "icons": [
- {
- "src": "/assets/icons/android-chrome-192x192.png",
- "type": "image/png",
- "sizes": "192x192"
- },
- {
- "src": "/assets/icons/android-chrome-512x512.png",
- "type": "image/png",
- "sizes": "512x512"
- }
- ]
-}
diff --git a/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx
index 9a81b72d4..bac7a39eb 100644
--- a/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx
@@ -26,7 +26,7 @@ import { Loading } from "../../../components/exception/loading.js";
import { NotificationCard } from "../../../components/menu/index.js";
import { DeleteModal, PurgeModal } from "../../../components/modal/index.js";
import { MerchantBackend } from "../../../declaration.js";
-import { HttpError } from "../../../hooks/backend.js";
+import { HttpError } from "../../../utils/request.js";
import { useAdminAPI, useBackendInstances } from "../../../hooks/instance.js";
import { Notification } from "../../../utils/types.js";
import { View } from "./View.js";
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx
index 49b64262b..56d5c0755 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx
@@ -18,7 +18,7 @@ import { useState } from "preact/hooks";
import { Loading } from "../../../components/exception/loading.js";
import { DeleteModal } from "../../../components/modal/index.js";
import { useInstanceContext } from "../../../context/instance.js";
-import { HttpError } from "../../../hooks/backend.js";
+import { HttpError } from "../../../utils/request.js";
import { useInstanceAPI, useInstanceDetails } from "../../../hooks/instance.js";
import { DetailPage } from "./DetailPage.js";
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx
index 295d6a749..83af002b3 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx
@@ -21,7 +21,7 @@
import { h, VNode } from "preact";
import { Loading } from "../../../../components/exception/loading.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
import { useInstanceKYCDetails } from "../../../../hooks/instance.js";
import { ListPage } from "./ListPage.js";
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx
index 95232da92..5c6293a81 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx
@@ -24,7 +24,7 @@ import { useState } from "preact/hooks";
import { Loading } from "../../../../components/exception/loading.js";
import { NotificationCard } from "../../../../components/menu/index.js";
import { MerchantBackend } from "../../../../declaration.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
import { useInstanceDetails } from "../../../../hooks/instance.js";
import { useOrderAPI } from "../../../../hooks/order.js";
import { useInstanceProducts } from "../../../../hooks/product.js";
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx
index bb0240982..19aaddf50 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx
@@ -18,7 +18,7 @@ import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { Loading } from "../../../../components/exception/loading.js";
import { NotificationCard } from "../../../../components/menu/index.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
import { useOrderAPI, useOrderDetails } from "../../../../hooks/order.js";
import { Notification } from "../../../../utils/types.js";
import { DetailPage } from "./DetailPage.js";
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx
index e29c57a7c..3744ce8c5 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx
@@ -25,7 +25,7 @@ import { useState } from "preact/hooks";
import { Loading } from "../../../../components/exception/loading.js";
import { NotificationCard } from "../../../../components/menu/index.js";
import { MerchantBackend } from "../../../../declaration.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
import {
InstanceOrderFilter,
useInstanceOrders,
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx
index 41a07a7aa..25332acee 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx
@@ -25,7 +25,7 @@ import { useState } from "preact/hooks";
import { Loading } from "../../../../components/exception/loading.js";
import { NotificationCard } from "../../../../components/menu/index.js";
import { MerchantBackend, WithId } from "../../../../declaration.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
import {
useInstanceProducts,
useProductAPI,
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx
index e141dc52c..5b19a7aa3 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx
@@ -25,7 +25,7 @@ import { useState } from "preact/hooks";
import { Loading } from "../../../../components/exception/loading.js";
import { NotificationCard } from "../../../../components/menu/index.js";
import { MerchantBackend } from "../../../../declaration.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
import { useProductAPI, useProductDetails } from "../../../../hooks/product.js";
import { Notification } from "../../../../utils/types.js";
import { UpdatePage } from "./UpdatePage.js";
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx
index de2319636..ad0cca74a 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx
@@ -31,7 +31,7 @@ import { Input } from "../../../../components/form/Input.js";
import { InputCurrency } from "../../../../components/form/InputCurrency.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
import { ExchangeBackend, MerchantBackend } from "../../../../declaration.js";
-import { request } from "../../../../hooks/backend.js";
+// import { request } from "../../../../utils/request.js";
import {
PAYTO_WIRE_METHOD_LOOKUP,
URL_REGEX,
@@ -124,11 +124,10 @@ function ViewStep({
<AsyncButton
class="has-tooltip-left"
onClick={() => {
- return request<ExchangeBackend.WireResponse>(
- `${reserve.exchange_url}wire`,
- )
+ return fetch(`${reserve.exchange_url}wire`)
+ .then((r) => r.json())
.then((r) => {
- const wireMethods = r.data.accounts.map((a) => {
+ const wireMethods = r.data.accounts.map((a: any) => {
const match = PAYTO_WIRE_METHOD_LOOKUP.exec(a.payto_uri);
return (match && match[1]) || "";
});
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/reserves/details/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/reserves/details/index.tsx
index b13b075fd..57ee566d1 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/reserves/details/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/reserves/details/index.tsx
@@ -21,7 +21,7 @@
import { Fragment, h, VNode } from "preact";
import { Loading } from "../../../../components/exception/loading.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
import { useReserveDetails } from "../../../../hooks/reserves.js";
import { DetailPage } from "./DetailPage.js";
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/index.tsx
index 9c3255ee8..597bde167 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/index.tsx
@@ -25,7 +25,7 @@ import { useState } from "preact/hooks";
import { Loading } from "../../../../components/exception/loading.js";
import { NotificationCard } from "../../../../components/menu/index.js";
import { MerchantBackend } from "../../../../declaration.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
import {
useInstanceReserves,
useReservesAPI,
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx
index dcac23983..e1a2d019e 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx
@@ -25,7 +25,7 @@ import { useState } from "preact/hooks";
import { Loading } from "../../../../components/exception/loading.js";
import { NotificationCard } from "../../../../components/menu/index.js";
import { MerchantBackend } from "../../../../declaration.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
import {
useInstanceTemplates,
useTemplateAPI,
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx
index 4a4cc4274..684ffd429 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx
@@ -25,7 +25,7 @@ import { useState } from "preact/hooks";
import { Loading } from "../../../../components/exception/loading.js";
import { NotificationCard } from "../../../../components/menu/index.js";
import { MerchantBackend, WithId } from "../../../../declaration.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
import {
useTemplateAPI,
useTemplateDetails,
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx
index 242380fbc..59b56a613 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx
@@ -23,7 +23,7 @@ import { h, VNode } from "preact";
import { useState } from "preact/hooks";
import { Loading } from "../../../../components/exception/loading.js";
import { MerchantBackend } from "../../../../declaration.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
import { useInstanceDetails } from "../../../../hooks/instance.js";
import { useInstanceTransfers } from "../../../../hooks/transfer.js";
import { ListPage } from "./ListPage.js";
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx
index 668fe9a8d..02beb36f2 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx
@@ -20,13 +20,13 @@ import { Loading } from "../../../components/exception/loading.js";
import { NotificationCard } from "../../../components/menu/index.js";
import { useInstanceContext } from "../../../context/instance.js";
import { MerchantBackend } from "../../../declaration.js";
-import { HttpError, HttpResponse } from "../../../hooks/backend.js";
import {
useInstanceAPI,
useInstanceDetails,
useManagedInstanceDetails,
useManagementAPI,
} from "../../../hooks/instance.js";
+import { HttpError, HttpResponse } from "../../../utils/request.js";
import { Notification } from "../../../utils/types.js";
import { UpdatePage } from "./UpdatePage.js";
diff --git a/packages/merchant-backoffice-ui/tests/functions/regex.test.ts b/packages/merchant-backoffice-ui/src/utils/regex.test.ts
index d866a13a0..41f0156f5 100644
--- a/packages/merchant-backoffice-ui/tests/functions/regex.test.ts
+++ b/packages/merchant-backoffice-ui/src/utils/regex.test.ts
@@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
+import { expect } from "chai";
import { AMOUNT_REGEX, PAYTO_REGEX } from "../../src/utils/constants.js";
describe('payto uri format', () => {
@@ -31,7 +32,7 @@ describe('payto uri format', () => {
]
it('should be valid', () => {
- valids.forEach(v => expect(v).toMatch(PAYTO_REGEX))
+ valids.forEach(v => expect(v).match(PAYTO_REGEX))
});
const invalids = [
@@ -48,7 +49,7 @@ describe('payto uri format', () => {
]
it('should not be valid', () => {
- invalids.forEach(v => expect(v).not.toMatch(PAYTO_REGEX))
+ invalids.forEach(v => expect(v).not.match(PAYTO_REGEX))
});
})
@@ -64,7 +65,7 @@ describe('amount format', () => {
]
it('should be valid', () => {
- valids.forEach(v => expect(v).toMatch(AMOUNT_REGEX))
+ valids.forEach(v => expect(v).match(AMOUNT_REGEX))
});
const invalids = [
@@ -81,7 +82,7 @@ describe('amount format', () => {
]
it('should not be valid', () => {
- invalids.forEach(v => expect(v).not.toMatch(AMOUNT_REGEX))
+ invalids.forEach(v => expect(v).not.match(AMOUNT_REGEX))
});
}) \ No newline at end of file
diff --git a/packages/merchant-backoffice-ui/src/utils/request.ts b/packages/merchant-backoffice-ui/src/utils/request.ts
new file mode 100644
index 000000000..32b31a557
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/utils/request.ts
@@ -0,0 +1,282 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 axios, { AxiosError, AxiosResponse } from "axios";
+import { MerchantBackend } from "../declaration.js";
+
+export async function defaultRequestHandler<T>(
+ base: string,
+ path: string,
+ options: RequestOptions = {},
+): Promise<HttpResponseOk<T>> {
+ const requestHeaders = options.token
+ ? { Authorization: `Bearer ${options.token}` }
+ : undefined;
+
+ const requestMethod = options?.method ?? "GET";
+ const requestBody = options?.data;
+ const requestTimeout = 2 * 1000;
+ const requestParams = options.params ?? {};
+
+ const _url = new URL(`${base}${path}`);
+
+ Object.entries(requestParams).forEach(([key, value]) => {
+ _url.searchParams.set(key, String(value));
+ });
+
+ let payload: BodyInit | undefined = undefined;
+ if (requestBody != null) {
+ if (typeof requestBody === "string") {
+ payload = requestBody;
+ } else if (requestBody instanceof ArrayBuffer) {
+ payload = requestBody;
+ } else if (ArrayBuffer.isView(requestBody)) {
+ payload = requestBody;
+ } else if (typeof requestBody === "object") {
+ payload = JSON.stringify(requestBody);
+ } else {
+ throw Error("unsupported request body type");
+ }
+ }
+
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => {
+ controller.abort("HTTP_REQUEST_TIMEOUT");
+ }, requestTimeout);
+
+ const response = await fetch(_url.href, {
+ headers: {
+ ...requestHeaders,
+ "Content-Type": "text/plain",
+ },
+ method: requestMethod,
+ credentials: "omit",
+ mode: "cors",
+ body: payload,
+ signal: controller.signal,
+ });
+
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+ const headerMap = new Headers();
+ response.headers.forEach((value, key) => {
+ headerMap.set(key, value);
+ });
+
+ if (response.ok) {
+ const result = await buildRequestOk<T>(
+ response,
+ _url,
+ payload,
+ !!options.token,
+ );
+ return result;
+ } else {
+ const error = await buildRequestFailed(
+ response,
+ _url,
+ payload,
+ !!options.token,
+ );
+ throw error;
+ }
+}
+
+export type HttpResponse<T> =
+ | HttpResponseOk<T>
+ | HttpResponseLoading<T>
+ | HttpError;
+export type HttpResponsePaginated<T> =
+ | HttpResponseOkPaginated<T>
+ | HttpResponseLoading<T>
+ | HttpError;
+
+export interface RequestInfo {
+ url: URL;
+ hasToken: boolean;
+ payload: any;
+ status: number;
+}
+
+interface HttpResponseLoading<T> {
+ ok?: false;
+ loading: true;
+ clientError?: false;
+ serverError?: false;
+
+ data?: T;
+}
+export interface HttpResponseOk<T> {
+ ok: true;
+ loading?: false;
+ clientError?: false;
+ serverError?: false;
+
+ data: T;
+ info?: RequestInfo;
+}
+
+export type HttpResponseOkPaginated<T> = HttpResponseOk<T> & WithPagination;
+
+export interface WithPagination {
+ loadMore: () => void;
+ loadMorePrev: () => void;
+ isReachingEnd?: boolean;
+ isReachingStart?: boolean;
+}
+
+export type HttpError =
+ | HttpResponseClientError
+ | HttpResponseServerError
+ | HttpResponseUnexpectedError;
+export interface SwrError {
+ info: unknown;
+ status: number;
+ message: string;
+}
+export interface HttpResponseServerError {
+ ok?: false;
+ loading?: false;
+ clientError?: false;
+ serverError: true;
+
+ error?: MerchantBackend.ErrorDetail;
+ status: number;
+ message: string;
+ info?: RequestInfo;
+}
+interface HttpResponseClientError {
+ ok?: false;
+ loading?: false;
+ clientError: true;
+ serverError?: false;
+
+ info?: RequestInfo;
+ isUnauthorized: boolean;
+ isNotfound: boolean;
+ status: number;
+ error?: MerchantBackend.ErrorDetail;
+ message: string;
+}
+
+interface HttpResponseUnexpectedError {
+ ok?: false;
+ loading?: false;
+ clientError?: false;
+ serverError?: false;
+
+ info?: RequestInfo;
+ status?: number;
+ error: unknown;
+ message: string;
+}
+
+type Methods = "GET" | "POST" | "PATCH" | "DELETE" | "PUT";
+
+export interface RequestOptions {
+ method?: Methods;
+ token?: string;
+ data?: any;
+ params?: unknown;
+}
+
+async function buildRequestOk<T>(
+ response: Response,
+ url: URL,
+ payload: any,
+ hasToken: boolean,
+): Promise<HttpResponseOk<T>> {
+ const dataTxt = await response.text();
+ const data = dataTxt ? JSON.parse(dataTxt) : undefined
+ return {
+ ok: true,
+ data,
+ info: {
+ payload,
+ url,
+ hasToken,
+ status: response.status,
+ },
+ };
+}
+
+async function buildRequestFailed(
+ response: Response,
+ url: URL,
+ payload: any,
+ hasToken: boolean,
+): Promise<
+ | HttpResponseClientError
+ | HttpResponseServerError
+ | HttpResponseUnexpectedError
+> {
+ const status = response?.status;
+
+ const info: RequestInfo = {
+ payload,
+ url,
+ hasToken,
+ status: status || 0,
+ };
+
+ try {
+ const dataTxt = await response.text();
+ const data = dataTxt ? JSON.parse(dataTxt) : undefined
+ if (status && status >= 400 && status < 500) {
+ const error: HttpResponseClientError = {
+ clientError: true,
+ isNotfound: status === 404,
+ isUnauthorized: status === 401,
+ status,
+ info,
+ message: data?.hint,
+ error: data,
+ };
+ return error;
+ }
+ if (status && status >= 500 && status < 600) {
+ const error: HttpResponseServerError = {
+ serverError: true,
+ status,
+ info,
+ message: `${data?.hint} (code ${data?.code})`,
+ error: data,
+ };
+ return error;
+ }
+ return {
+ info,
+ status,
+ error: {},
+ message: "NOT DEFINED",
+ };
+ } catch (ex) {
+ const error: HttpResponseUnexpectedError = {
+ info,
+ status,
+ error: ex,
+ message: "NOT DEFINED",
+ };
+
+ throw error;
+ }
+}
+
+// export function isAxiosError<T>(
+// error: AxiosError | any,
+// ): error is AxiosError<T> {
+// return error && error.isAxiosError;
+// }
diff --git a/packages/merchant-backoffice-ui/src/utils/switchableAxios.ts b/packages/merchant-backoffice-ui/src/utils/switchableAxios.ts
deleted file mode 100644
index 20ce7043e..000000000
--- a/packages/merchant-backoffice-ui/src/utils/switchableAxios.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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 axios, { AxiosPromise, AxiosRequestConfig } from "axios";
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-export let removeAxiosCancelToken = false;
-
-export let axiosHandler = function doAxiosRequest(
- config: AxiosRequestConfig,
-): AxiosPromise<any> {
- return axios(config);
-};
-
-/**
- * Set this backend library to testing mode.
- * Instead of calling the axios library the @handler will be called
- *
- * @param handler callback that will mock axios
- */
-export function setAxiosRequestAsTestingEnvironment(
- handler: AxiosHandler,
-): void {
- removeAxiosCancelToken = true;
- axiosHandler = function defaultTestingHandler(config) {
- const currentHanlder = listOfHandlersToUseOnce.shift();
- if (!currentHanlder) {
- return handler(config);
- }
-
- return currentHanlder(config);
- };
-}
-
-type AxiosHandler = (config: AxiosRequestConfig) => AxiosPromise<any>;
-type AxiosArguments = { args: AxiosRequestConfig | undefined };
-
-const listOfHandlersToUseOnce = new Array<AxiosHandler>();
-
-/**
- *
- * @param handler mock function
- * @returns savedArgs
- */
-export function mockAxiosOnce(handler: AxiosHandler): {
- args: AxiosRequestConfig | undefined;
-} {
- const savedArgs: AxiosArguments = { args: undefined };
- listOfHandlersToUseOnce.push(
- (config: AxiosRequestConfig): AxiosPromise<any> => {
- savedArgs.args = config;
- return handler(config);
- },
- );
- return savedArgs;
-}
diff --git a/packages/merchant-backoffice-ui/tests/__mocks__/fileMocks.ts b/packages/merchant-backoffice-ui/tests/__mocks__/fileMocks.ts
deleted file mode 100644
index 982832ea8..000000000
--- a/packages/merchant-backoffice-ui/tests/__mocks__/fileMocks.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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/>
- */
-
- /**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-// This fixed an error related to the CSS and loading gif breaking my Jest test
-// See https://facebook.github.io/jest/docs/en/webpack.html#handling-static-assets
-export default 'test-file-stub';
diff --git a/packages/merchant-backoffice-ui/tests/__mocks__/fileTransformer.js b/packages/merchant-backoffice-ui/tests/__mocks__/fileTransformer.js
deleted file mode 100644
index b76da9168..000000000
--- a/packages/merchant-backoffice-ui/tests/__mocks__/fileTransformer.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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/>
- */
-
-/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-// fileTransformer.js
-
-// eslint-disable-next-line @typescript-eslint/no-var-requires
-const path = require('path');
-
-module.exports = {
- process(src, filename, config, options) {
- return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
- },
-};
-
diff --git a/packages/merchant-backoffice-ui/tests/__mocks__/setupTests.ts b/packages/merchant-backoffice-ui/tests/__mocks__/setupTests.ts
deleted file mode 100644
index fe2d72d5c..000000000
--- a/packages/merchant-backoffice-ui/tests/__mocks__/setupTests.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import "regenerator-runtime/runtime";
-// import { configure } from 'enzyme';
-// import Adapter from 'enzyme-adapter-preact-pure';
-
-// configure({
-// adapter: new Adapter()
-// });
diff --git a/packages/merchant-backoffice-ui/tests/axiosMock.ts b/packages/merchant-backoffice-ui/tests/axiosMock.ts
deleted file mode 100644
index ca8d5096d..000000000
--- a/packages/merchant-backoffice-ui/tests/axiosMock.ts
+++ /dev/null
@@ -1,445 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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/>
- */
-
-/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-import * as axios from 'axios';
-import { MerchantBackend } from "../src/declaration.js";
-import { mockAxiosOnce, setAxiosRequestAsTestingEnvironment } from "../src/utils/switchableAxios.js";
-// import { mockAxiosOnce, setAxiosRequestAsTestingEnvironment } from "../src/hooks/backend.js";
-
-export type Query<Req, Res> = (GetQuery | PostQuery | DeleteQuery | PatchQuery) & RequestResponse<Req, Res>
-
-interface RequestResponse<Req, Res> {
- code?: number,
-}
-interface GetQuery { get: string }
-interface PostQuery { post: string }
-interface DeleteQuery { delete: string }
-interface PatchQuery { patch: string }
-
-
-const JEST_DEBUG_LOG = process.env['JEST_DEBUG_LOG'] !== undefined
-
-type ExpectationValues = { query: Query<any, any>; params?: { auth?: string, request?: any, qparam?: any, response?: any } }
-
-type TestValues = [axios.AxiosRequestConfig | undefined, ExpectationValues | undefined]
-
-const defaultCallback = (actualQuery?: axios.AxiosRequestConfig): axios.AxiosPromise<any> => {
- if (JEST_DEBUG_LOG) {
- console.log('UNEXPECTED QUERY', actualQuery)
- }
- throw Error('Default Axios mock callback is called, this mean that the test did a tried to use axios but there was no expectation in place, try using JEST_DEBUG_LOG env')
-}
-
-setAxiosRequestAsTestingEnvironment(
- defaultCallback
-);
-
-export class AxiosMockEnvironment {
- expectations: Array<{
- query: Query<any, any>,
- auth?: string,
- params?: { request?: any, qparam?: any, response?: any },
- result: { args: axios.AxiosRequestConfig | undefined }
- } | undefined> = []
- // axiosMock: jest.MockedFunction<axios.AxiosStatic>
-
- addRequestExpectation<RequestType, ResponseType>(expectedQuery: Query<RequestType, ResponseType>, params: { auth?: string, request?: RequestType, qparam?: any, response?: ResponseType }): void {
- const result = mockAxiosOnce(function (actualQuery?: axios.AxiosRequestConfig): axios.AxiosPromise {
-
- if (JEST_DEBUG_LOG) {
- console.log('query to the backend is made', actualQuery)
- }
- if (!expectedQuery) {
- return Promise.reject("a query was made but it was not expected")
- }
- if (JEST_DEBUG_LOG) {
- console.log('expected query:', params?.request)
- console.log('expected qparams:', params?.qparam)
- console.log('sending response:', params?.response)
- }
-
- const responseCode = expectedQuery.code || 200
-
- //This response is what buildRequestOk is expecting in file hook/backend.ts
- if (responseCode >= 200 && responseCode < 300) {
- return Promise.resolve({
- data: params?.response, config: {
- data: params?.response,
- params: actualQuery?.params || {},
- }, request: { params: actualQuery?.params || {} }
- } as any);
- }
- //This response is what buildRequestFailed is expecting in file hook/backend.ts
- return Promise.reject({
- response: {
- status: responseCode
- },
- request: {
- data: params?.response,
- params: actualQuery?.params || {},
- }
- })
-
- } as any)
-
- this.expectations.push(expectedQuery ? { query: expectedQuery, params, result } : undefined)
- }
-
- getLastTestValues(): TestValues {
- const expectedQuery = this.expectations.shift()
-
- return [
- expectedQuery?.result.args, expectedQuery
- ]
- }
-
-}
-
-export function assertJustExpectedRequestWereMade(env: AxiosMockEnvironment): void {
- let size = env.expectations.length
- while (size-- > 0) {
- assertNextRequest(env)
- }
- assertNoMoreRequestWereMade(env)
-}
-
-export function assertNoMoreRequestWereMade(env: AxiosMockEnvironment): void {
- const [actualQuery, expectedQuery] = env.getLastTestValues()
-
- expect(actualQuery).toBeUndefined();
- expect(expectedQuery).toBeUndefined();
-}
-
-export function assertNextRequest(env: AxiosMockEnvironment): void {
- const [actualQuery, expectedQuery] = env.getLastTestValues()
-
- if (!actualQuery) {
- //expected one query but the tested component didn't execute one
- expect(actualQuery).toBe(expectedQuery);
- return
- }
-
- if (!expectedQuery) {
- const errorMessage = 'a query was made to the backend but the test explicitly expected no query';
- if (JEST_DEBUG_LOG) {
- console.log(errorMessage, actualQuery)
- }
- throw Error(errorMessage)
- }
- if ('get' in expectedQuery.query) {
- expect(actualQuery.method).toBe('get');
- expect(actualQuery.url).toBe(expectedQuery.query.get);
- }
- if ('post' in expectedQuery.query) {
- expect(actualQuery.method).toBe('post');
- expect(actualQuery.url).toBe(expectedQuery.query.post);
- }
- if ('delete' in expectedQuery.query) {
- expect(actualQuery.method).toBe('delete');
- expect(actualQuery.url).toBe(expectedQuery.query.delete);
- }
- if ('patch' in expectedQuery.query) {
- expect(actualQuery.method).toBe('patch');
- expect(actualQuery.url).toBe(expectedQuery.query.patch);
- }
-
- if (expectedQuery.params?.request) {
- expect(actualQuery.data).toMatchObject(expectedQuery.params.request)
- }
- if (expectedQuery.params?.qparam) {
- expect(actualQuery.params).toMatchObject(expectedQuery.params.qparam)
- }
-
- if (expectedQuery.params?.auth) {
- expect(actualQuery.headers.Authorization).toBe(expectedQuery.params?.auth)
- }
-
-}
-
-////////////////////
-// ORDER
-////////////////////
-
-export const API_CREATE_ORDER: Query<
- MerchantBackend.Orders.PostOrderRequest,
- MerchantBackend.Orders.PostOrderResponse
-> = {
- post: "http://backend/instances/default/private/orders",
-};
-
-export const API_GET_ORDER_BY_ID = (
- id: string
-): Query<
- unknown,
- MerchantBackend.Orders.MerchantOrderStatusResponse
-> => ({
- get: `http://backend/instances/default/private/orders/${id}`,
-});
-
-export const API_LIST_ORDERS: Query<
- unknown,
- MerchantBackend.Orders.OrderHistory
-> = {
- get: "http://backend/instances/default/private/orders",
-};
-
-export const API_REFUND_ORDER_BY_ID = (
- id: string
-): Query<
- MerchantBackend.Orders.RefundRequest,
- MerchantBackend.Orders.MerchantRefundResponse
-> => ({
- post: `http://backend/instances/default/private/orders/${id}/refund`,
-});
-
-export const API_FORGET_ORDER_BY_ID = (
- id: string
-): Query<
- MerchantBackend.Orders.ForgetRequest,
- unknown
-> => ({
- patch: `http://backend/instances/default/private/orders/${id}/forget`,
-});
-
-export const API_DELETE_ORDER = (
- id: string
-): Query<
- MerchantBackend.Orders.ForgetRequest,
- unknown
-> => ({
- delete: `http://backend/instances/default/private/orders/${id}`,
-});
-
-////////////////////
-// TRANSFER
-////////////////////
-
-export const API_LIST_TRANSFERS: Query<
- unknown,
- MerchantBackend.Transfers.TransferList
-> = {
- get: "http://backend/instances/default/private/transfers",
-};
-
-export const API_INFORM_TRANSFERS: Query<
- MerchantBackend.Transfers.TransferInformation,
- MerchantBackend.Transfers.MerchantTrackTransferResponse
-> = {
- post: "http://backend/instances/default/private/transfers",
-};
-
-////////////////////
-// PRODUCT
-////////////////////
-
-export const API_CREATE_PRODUCT: Query<
- MerchantBackend.Products.ProductAddDetail,
- unknown
-> = {
- post: "http://backend/instances/default/private/products",
-};
-
-export const API_LIST_PRODUCTS: Query<
- unknown,
- MerchantBackend.Products.InventorySummaryResponse
-> = {
- get: "http://backend/instances/default/private/products",
-};
-
-export const API_GET_PRODUCT_BY_ID = (
- id: string
-): Query<unknown, MerchantBackend.Products.ProductDetail> => ({
- get: `http://backend/instances/default/private/products/${id}`,
-});
-
-export const API_UPDATE_PRODUCT_BY_ID = (
- id: string
-): Query<
- MerchantBackend.Products.ProductPatchDetail,
- MerchantBackend.Products.InventorySummaryResponse
-> => ({
- patch: `http://backend/instances/default/private/products/${id}`,
-});
-
-export const API_DELETE_PRODUCT = (
- id: string
-): Query<
- unknown, unknown
-> => ({
- delete: `http://backend/instances/default/private/products/${id}`,
-});
-
-////////////////////
-// RESERVES
-////////////////////
-
-export const API_CREATE_RESERVE: Query<
- MerchantBackend.Tips.ReserveCreateRequest,
- MerchantBackend.Tips.ReserveCreateConfirmation
-> = {
- post: "http://backend/instances/default/private/reserves",
-};
-export const API_LIST_RESERVES: Query<
- unknown,
- MerchantBackend.Tips.TippingReserveStatus
-> = {
- get: "http://backend/instances/default/private/reserves",
-};
-
-export const API_GET_RESERVE_BY_ID = (
- pub: string
-): Query<unknown, MerchantBackend.Tips.ReserveDetail> => ({
- get: `http://backend/instances/default/private/reserves/${pub}`,
-});
-
-export const API_GET_TIP_BY_ID = (
- pub: string
-): Query<
- unknown,
- MerchantBackend.Tips.TipDetails
-> => ({
- get: `http://backend/instances/default/private/tips/${pub}`,
-});
-
-export const API_AUTHORIZE_TIP_FOR_RESERVE = (
- pub: string
-): Query<
- MerchantBackend.Tips.TipCreateRequest,
- MerchantBackend.Tips.TipCreateConfirmation
-> => ({
- post: `http://backend/instances/default/private/reserves/${pub}/authorize-tip`,
-});
-
-export const API_AUTHORIZE_TIP: Query<
- MerchantBackend.Tips.TipCreateRequest,
- MerchantBackend.Tips.TipCreateConfirmation
-> = ({
- post: `http://backend/instances/default/private/tips`,
-});
-
-
-export const API_DELETE_RESERVE = (
- id: string
-): Query<unknown, unknown> => ({
- delete: `http://backend/instances/default/private/reserves/${id}`,
-});
-
-
-////////////////////
-// INSTANCE ADMIN
-////////////////////
-
-export const API_CREATE_INSTANCE: Query<
- MerchantBackend.Instances.InstanceConfigurationMessage,
- unknown
-> = {
- post: "http://backend/management/instances",
-};
-
-export const API_GET_INSTANCE_BY_ID = (
- id: string
-): Query<
- unknown,
- MerchantBackend.Instances.QueryInstancesResponse
-> => ({
- get: `http://backend/management/instances/${id}`,
-});
-
-export const API_GET_INSTANCE_KYC_BY_ID = (
- id: string
-): Query<
- unknown,
- MerchantBackend.Instances.AccountKycRedirects
-> => ({
- get: `http://backend/management/instances/${id}/kyc`,
-});
-
-export const API_LIST_INSTANCES: Query<
- unknown,
- MerchantBackend.Instances.InstancesResponse
-> = {
- get: "http://backend/management/instances",
-};
-
-export const API_UPDATE_INSTANCE_BY_ID = (
- id: string
-): Query<
- MerchantBackend.Instances.InstanceReconfigurationMessage,
- unknown
-> => ({
- patch: `http://backend/management/instances/${id}`,
-});
-
-export const API_UPDATE_INSTANCE_AUTH_BY_ID = (
- id: string
-): Query<
- MerchantBackend.Instances.InstanceAuthConfigurationMessage,
- unknown
-> => ({
- post: `http://backend/management/instances/${id}/auth`,
-});
-
-export const API_DELETE_INSTANCE = (
- id: string
-): Query<unknown, unknown> => ({
- delete: `http://backend/management/instances/${id}`,
-});
-
-////////////////////
-// INSTANCE
-////////////////////
-
-export const API_GET_CURRENT_INSTANCE: Query<
- unknown,
- MerchantBackend.Instances.QueryInstancesResponse
-> = ({
- get: `http://backend/instances/default/private/`,
-});
-
-export const API_GET_CURRENT_INSTANCE_KYC: Query<
- unknown,
- MerchantBackend.Instances.AccountKycRedirects
-> =
- ({
- get: `http://backend/instances/default/private/kyc`,
- });
-
-export const API_UPDATE_CURRENT_INSTANCE: Query<
- MerchantBackend.Instances.InstanceReconfigurationMessage,
- unknown
-> = {
- patch: `http://backend/instances/default/private/`,
-};
-
-export const API_UPDATE_CURRENT_INSTANCE_AUTH: Query<
- MerchantBackend.Instances.InstanceAuthConfigurationMessage,
- unknown
-> = {
- post: `http://backend/instances/default/private/auth`,
-};
-
-export const API_DELETE_CURRENT_INSTANCE: Query<
- unknown,
- unknown
-> = ({
- delete: `http://backend/instances/default/private`,
-});
-
-
diff --git a/packages/merchant-backoffice-ui/tests/context/backend.test.tsx b/packages/merchant-backoffice-ui/tests/context/backend.test.tsx
deleted file mode 100644
index 671c19d0b..000000000
--- a/packages/merchant-backoffice-ui/tests/context/backend.test.tsx
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { renderHook } from "@testing-library/preact-hooks";
-import { ComponentChildren, h, VNode } from "preact";
-import { act } from "preact/test-utils";
-import { BackendContextProvider } from "../../src/context/backend.js";
-import { InstanceContextProvider } from "../../src/context/instance.js";
-import { MerchantBackend } from "../../src/declaration.js";
-import {
- useAdminAPI,
- useInstanceAPI,
- useManagementAPI,
-} from "../../src/hooks/instance.js";
-import {
- API_CREATE_INSTANCE,
- API_GET_CURRENT_INSTANCE,
- API_UPDATE_CURRENT_INSTANCE_AUTH,
- API_UPDATE_INSTANCE_AUTH_BY_ID,
- assertJustExpectedRequestWereMade,
- AxiosMockEnvironment,
-} from "../axiosMock.js";
-
-interface TestingContextProps {
- children?: ComponentChildren;
-}
-
-function TestingContext({ children }: TestingContextProps): VNode {
- return (
- <BackendContextProvider defaultUrl="http://backend" initialToken="token">
- {children}
- </BackendContextProvider>
- );
-}
-function AdminTestingContext({ children }: TestingContextProps): VNode {
- return (
- <BackendContextProvider defaultUrl="http://backend" initialToken="token">
- <InstanceContextProvider
- value={{
- token: "token",
- id: "default",
- admin: true,
- changeToken: () => null,
- }}
- >
- {children}
- </InstanceContextProvider>
- </BackendContextProvider>
- );
-}
-
-describe("backend context api ", () => {
- it("should use new token after updating the instance token in the settings as user", async () => {
- const env = new AxiosMockEnvironment();
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- const instance = useInstanceAPI();
- const management = useManagementAPI("default");
- const admin = useAdminAPI();
-
- return { instance, management, admin };
- },
- { wrapper: TestingContext }
- );
-
- if (!result.current) {
- expect(result.current).toBeDefined();
- return;
- }
-
- env.addRequestExpectation(API_UPDATE_INSTANCE_AUTH_BY_ID("default"), {
- request: {
- method: "token",
- token: "another_token",
- },
- response: {
- name: "instance_name",
- } as MerchantBackend.Instances.QueryInstancesResponse,
- });
-
- await act(async () => {
- await result.current?.management.setNewToken("another_token");
- });
-
- // await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- env.addRequestExpectation(API_CREATE_INSTANCE, {
- auth: "Bearer another_token",
- request: {
- id: "new_instance_id",
- } as MerchantBackend.Instances.InstanceConfigurationMessage,
- });
-
- result.current.admin.createInstance({
- id: "new_instance_id",
- } as MerchantBackend.Instances.InstanceConfigurationMessage);
-
- assertJustExpectedRequestWereMade(env);
- });
-
- it("should use new token after updating the instance token in the settings as admin", async () => {
- const env = new AxiosMockEnvironment();
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- const instance = useInstanceAPI();
- const management = useManagementAPI("default");
- const admin = useAdminAPI();
-
- return { instance, management, admin };
- },
- { wrapper: AdminTestingContext }
- );
-
- if (!result.current) {
- expect(result.current).toBeDefined();
- return;
- }
-
- env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE_AUTH, {
- request: {
- method: "token",
- token: "another_token",
- },
- response: {
- name: "instance_name",
- } as MerchantBackend.Instances.QueryInstancesResponse,
- });
-
- await act(async () => {
- await result.current?.instance.setNewToken("another_token");
- });
-
- // await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- env.addRequestExpectation(API_CREATE_INSTANCE, {
- auth: "Bearer another_token",
- request: {
- id: "new_instance_id",
- } as MerchantBackend.Instances.InstanceConfigurationMessage,
- });
-
- result.current.admin.createInstance({
- id: "new_instance_id",
- } as MerchantBackend.Instances.InstanceConfigurationMessage);
-
- assertJustExpectedRequestWereMade(env);
- });
-});
diff --git a/packages/merchant-backoffice-ui/tests/declarations.d.ts b/packages/merchant-backoffice-ui/tests/declarations.d.ts
deleted file mode 100644
index 677aa9f24..000000000
--- a/packages/merchant-backoffice-ui/tests/declarations.d.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-declare global {
- namespace jest {
- interface Matchers<R> {
- toBeWithinRange(a: number, b: number): R;
- }
- }
-}
diff --git a/packages/merchant-backoffice-ui/tests/header.test.tsx b/packages/merchant-backoffice-ui/tests/header.test.tsx
deleted file mode 100644
index 1cf2b7e6c..000000000
--- a/packages/merchant-backoffice-ui/tests/header.test.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { h } from "preact";
-import { ProductList } from "../src/components/product/ProductList.js";
-// See: https://github.com/preactjs/enzyme-adapter-preact-pure
-// import { shallow } from 'enzyme';
-import { render } from "@testing-library/preact";
-import * as backend from "../src/context/config.js";
-// import * as i18n from "../src/context/translation.js";
-
-// import * as jedLib from "jed";
-// const handler = new jedLib.Jed("en");
-
-describe("Initial Test of the Sidebar", () => {
- beforeEach(() => {
- jest
- .spyOn(backend, "useConfigContext")
- .mockImplementation(() => ({ version: "", currency: "" }));
- // jest.spyOn(i18n, "useTranslationContext").mockImplementation(() => ({
- // changeLanguage: () => null,
- // handler,
- // lang: "en",
- // }));
- });
- test("Product list renders a table", () => {
- const context = render(
- <ProductList
- list={[
- {
- description: "description of the product",
- image: "asdasda",
- price: "USD:10",
- quantity: 1,
- taxes: [{ name: "VAT", tax: "EUR:1" }],
- unit: "book",
- },
- ]}
- />,
- );
-
- expect(context.findAllByText("description of the product")).toBeDefined();
- // expect(context.find('table tr td img').map(img => img.prop('src'))).toEqual('');
- });
-});
diff --git a/packages/merchant-backoffice-ui/tests/hooks/async.test.ts b/packages/merchant-backoffice-ui/tests/hooks/async.test.ts
deleted file mode 100644
index 18cfc5c55..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/async.test.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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 { renderHook } from "@testing-library/preact-hooks"
-import { useAsync } from "../../src/hooks/async.js"
-
-/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-test("async function is called", async () => {
- jest.useFakeTimers()
-
- const timeout = 500
-
- const asyncFunction = jest.fn(() => new Promise((res) => {
- setTimeout(() => {
- res({ the_answer: 'yes' })
- }, timeout);
- }))
-
- const { result, waitForNextUpdate } = renderHook(() => {
- return useAsync(asyncFunction)
- })
-
- expect(result.current?.isLoading).toBeFalsy()
-
- result.current?.request()
- expect(asyncFunction).toBeCalled()
- await waitForNextUpdate({ timeout: 1 })
- expect(result.current?.isLoading).toBeTruthy()
-
- jest.advanceTimersByTime(timeout + 1)
- await waitForNextUpdate({ timeout: 1 })
- expect(result.current?.isLoading).toBeFalsy()
- expect(result.current?.data).toMatchObject({ the_answer: 'yes' })
- expect(result.current?.error).toBeUndefined()
- expect(result.current?.isSlow).toBeFalsy()
-})
-
-test("async function return error if rejected", async () => {
- jest.useFakeTimers()
-
- const timeout = 500
-
- const asyncFunction = jest.fn(() => new Promise((_, rej) => {
- setTimeout(() => {
- rej({ the_error: 'yes' })
- }, timeout);
- }))
-
- const { result, waitForNextUpdate } = renderHook(() => {
- return useAsync(asyncFunction)
- })
-
- expect(result.current?.isLoading).toBeFalsy()
-
- result.current?.request()
- expect(asyncFunction).toBeCalled()
- await waitForNextUpdate({ timeout: 1 })
- expect(result.current?.isLoading).toBeTruthy()
-
- jest.advanceTimersByTime(timeout + 1)
- await waitForNextUpdate({ timeout: 1 })
- expect(result.current?.isLoading).toBeFalsy()
- expect(result.current?.error).toMatchObject({ the_error: 'yes' })
- expect(result.current?.data).toBeUndefined()
- expect(result.current?.isSlow).toBeFalsy()
-})
-
-test("async function is slow", async () => {
- jest.useFakeTimers()
-
- const timeout = 2200
-
- const asyncFunction = jest.fn(() => new Promise((res) => {
- setTimeout(() => {
- res({ the_answer: 'yes' })
- }, timeout);
- }))
-
- const { result, waitForNextUpdate } = renderHook(() => {
- return useAsync(asyncFunction)
- })
-
- expect(result.current?.isLoading).toBeFalsy()
-
- result.current?.request()
- expect(asyncFunction).toBeCalled()
- await waitForNextUpdate({ timeout: 1 })
- expect(result.current?.isLoading).toBeTruthy()
-
- jest.advanceTimersByTime(timeout / 2)
- await waitForNextUpdate({ timeout: 1 })
- expect(result.current?.isLoading).toBeTruthy()
- expect(result.current?.isSlow).toBeTruthy()
- expect(result.current?.data).toBeUndefined()
- expect(result.current?.error).toBeUndefined()
-
- jest.advanceTimersByTime(timeout / 2)
- await waitForNextUpdate({ timeout: 1 })
- expect(result.current?.isLoading).toBeFalsy()
- expect(result.current?.data).toMatchObject({ the_answer: 'yes' })
- expect(result.current?.error).toBeUndefined()
- expect(result.current?.isSlow).toBeFalsy()
-
-})
-
-test("async function is cancellable", async () => {
- jest.useFakeTimers()
-
- const timeout = 2200
-
- const asyncFunction = jest.fn(() => new Promise((res) => {
- setTimeout(() => {
- res({ the_answer: 'yes' })
- }, timeout);
- }))
-
- const { result, waitForNextUpdate } = renderHook(() => {
- return useAsync(asyncFunction)
- })
-
- expect(result.current?.isLoading).toBeFalsy()
-
- result.current?.request()
- expect(asyncFunction).toBeCalled()
- await waitForNextUpdate({ timeout: 1 })
- expect(result.current?.isLoading).toBeTruthy()
-
- jest.advanceTimersByTime(timeout / 2)
- await waitForNextUpdate({ timeout: 1 })
- expect(result.current?.isLoading).toBeTruthy()
- expect(result.current?.isSlow).toBeTruthy()
- expect(result.current?.data).toBeUndefined()
- expect(result.current?.error).toBeUndefined()
-
- result.current?.cancel()
- await waitForNextUpdate({ timeout: 1 })
- expect(result.current?.isLoading).toBeFalsy()
- expect(result.current?.data).toBeUndefined()
- expect(result.current?.error).toBeUndefined()
- expect(result.current?.isSlow).toBeFalsy()
-
-})
diff --git a/packages/merchant-backoffice-ui/tests/hooks/listener.test.ts b/packages/merchant-backoffice-ui/tests/hooks/listener.test.ts
deleted file mode 100644
index 8afd5f8d1..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/listener.test.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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/>
- */
-
-/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-
-import { renderHook, act } from '@testing-library/preact-hooks';
-import { useListener } from "../../src/hooks/listener.js";
-
-// jest.useFakeTimers()
-
-test('listener', async () => {
-
-
- function createSomeString() {
- return "hello"
- }
- async function addWorldToTheEnd(resultFromComponentB: string) {
- return `${resultFromComponentB} world`
- }
- const expectedResult = "hello world"
-
- const { result } = renderHook(() => useListener(addWorldToTheEnd))
-
- expect(result.current).toBeDefined()
- if (!result.current) {
- return;
- }
-
- {
- const [activator, subscriber] = result.current
- expect(activator).toBeUndefined()
-
- act(() => {
- subscriber(createSomeString)
- })
-
- }
-
- const [activator] = result.current
- expect(activator).toBeDefined()
- if (!activator) return;
-
- const response = await activator()
- expect(response).toBe(expectedResult)
-
-});
diff --git a/packages/merchant-backoffice-ui/tests/hooks/notification.test.ts b/packages/merchant-backoffice-ui/tests/hooks/notification.test.ts
deleted file mode 100644
index 801aa0e2e..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/notification.test.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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/>
- */
-
- /**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { renderHook, act} from '@testing-library/preact-hooks';
-import { useNotifications } from "../../src/hooks/notifications.js";
-
-jest.useFakeTimers()
-
-test('notification should disappear after timeout', () => {
- jest.spyOn(global, 'setTimeout');
-
- const timeout = 1000
- const { result, rerender } = renderHook(() => useNotifications(undefined, timeout));
-
- expect(result.current?.notifications.length).toBe(0);
-
- act(() => {
- result.current?.pushNotification({
- message: 'some_id',
- type: 'INFO'
- });
- });
- expect(result.current?.notifications.length).toBe(1);
-
- jest.advanceTimersByTime(timeout/2);
- rerender()
- expect(result.current?.notifications.length).toBe(1);
-
- jest.advanceTimersByTime(timeout);
- rerender()
- expect(result.current?.notifications.length).toBe(0);
-
-});
diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/index.tsx b/packages/merchant-backoffice-ui/tests/hooks/swr/index.tsx
deleted file mode 100644
index 2608523e6..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/swr/index.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { ComponentChildren, h, VNode } from "preact";
-import { SWRConfig } from "swr";
-import { BackendContextProvider } from "../../../src/context/backend.js";
-import { InstanceContextProvider } from "../../../src/context/instance.js";
-
-interface TestingContextProps {
- children?: ComponentChildren;
-}
-export function TestingContext({ children }: TestingContextProps): VNode {
- const SC: any = SWRConfig
- return (
- <BackendContextProvider defaultUrl="http://backend" initialToken="token">
- <InstanceContextProvider
- value={{
- token: "token",
- id: "default",
- admin: true,
- changeToken: () => null,
- }}
- >
- <SC value={{ provider: () => new Map() }}>{children}</SC>
- </InstanceContextProvider>
- </BackendContextProvider>
- );
-}
diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/instance.test.ts b/packages/merchant-backoffice-ui/tests/hooks/swr/instance.test.ts
deleted file mode 100644
index 36a2f7241..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/swr/instance.test.ts
+++ /dev/null
@@ -1,636 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { renderHook } from "@testing-library/preact-hooks";
-import { act } from "preact/test-utils";
-import { MerchantBackend } from "../../../src/declaration.js";
-import { useAdminAPI, useBackendInstances, useInstanceAPI, useInstanceDetails, useManagementAPI } from "../../../src/hooks/instance.js";
-import {
- API_CREATE_INSTANCE,
- API_DELETE_INSTANCE,
- API_GET_CURRENT_INSTANCE,
- API_LIST_INSTANCES,
- API_UPDATE_CURRENT_INSTANCE,
- API_UPDATE_CURRENT_INSTANCE_AUTH,
- API_UPDATE_INSTANCE_AUTH_BY_ID,
- API_UPDATE_INSTANCE_BY_ID,
- assertJustExpectedRequestWereMade,
- AxiosMockEnvironment
-} from "../../axiosMock.js";
-import { TestingContext } from "./index.js";
-
-describe("instance api interaction with details", () => {
-
- it("should evict cache when updating an instance", async () => {
-
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
- response: {
- name: 'instance_name'
- } as MerchantBackend.Instances.QueryInstancesResponse,
- });
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- const api = useInstanceAPI();
- const query = useInstanceDetails();
-
- return { query, api };
- },
- { wrapper: TestingContext }
- );
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
- expect(result.current.query.loading).toBeTruthy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
-
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- name: 'instance_name'
- });
-
- env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE, {
- request: {
- name: 'other_name'
- } as MerchantBackend.Instances.InstanceReconfigurationMessage,
- });
-
- act(async () => {
- await result.current?.api.updateInstance({
- name: 'other_name'
- } as MerchantBackend.Instances.InstanceReconfigurationMessage);
- });
-
- assertJustExpectedRequestWereMade(env);
-
- env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
- response: {
- name: 'other_name'
- } as MerchantBackend.Instances.QueryInstancesResponse,
- });
-
- expect(result.current.query.loading).toBeFalsy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current.query.ok).toBeTruthy();
-
- expect(result.current.query.data).toEqual({
- name: 'other_name'
- });
- });
-
- it("should evict cache when setting the instance's token", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
- response: {
- name: 'instance_name',
- auth: {
- method: 'token',
- token: 'not-secret',
- }
- } as MerchantBackend.Instances.QueryInstancesResponse,
- });
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- const api = useInstanceAPI();
- const query = useInstanceDetails();
-
- return { query, api };
- },
- { wrapper: TestingContext }
- );
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
- expect(result.current.query.loading).toBeTruthy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
-
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- name: 'instance_name',
- auth: {
- method: 'token',
- token: 'not-secret',
- }
- });
-
- env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE_AUTH, {
- request: {
- method: 'token',
- token: 'secret'
- } as MerchantBackend.Instances.InstanceAuthConfigurationMessage,
- });
-
- act(async () => {
- await result.current?.api.setNewToken('secret');
- });
-
- assertJustExpectedRequestWereMade(env);
-
- env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
- response: {
- name: 'instance_name',
- auth: {
- method: 'token',
- token: 'secret',
- }
- } as MerchantBackend.Instances.QueryInstancesResponse,
- });
-
- expect(result.current.query.loading).toBeFalsy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current.query.ok).toBeTruthy();
-
- expect(result.current.query.data).toEqual({
- name: 'instance_name',
- auth: {
- method: 'token',
- token: 'secret',
- }
- });
- });
-
- it("should evict cache when clearing the instance's token", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
- response: {
- name: 'instance_name',
- auth: {
- method: 'token',
- token: 'not-secret',
- }
- } as MerchantBackend.Instances.QueryInstancesResponse,
- });
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- const api = useInstanceAPI();
- const query = useInstanceDetails();
-
- return { query, api };
- },
- { wrapper: TestingContext }
- );
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
- expect(result.current.query.loading).toBeTruthy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
-
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- name: 'instance_name',
- auth: {
- method: 'token',
- token: 'not-secret',
- }
- });
-
- env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE_AUTH, {
- request: {
- method: 'external',
- } as MerchantBackend.Instances.InstanceAuthConfigurationMessage,
- });
-
- act(async () => {
- await result.current?.api.clearToken();
- });
-
- assertJustExpectedRequestWereMade(env);
-
- env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
- response: {
- name: 'instance_name',
- auth: {
- method: 'external',
- }
- } as MerchantBackend.Instances.QueryInstancesResponse,
- });
-
- expect(result.current.query.loading).toBeFalsy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current.query.ok).toBeTruthy();
-
- expect(result.current.query.data).toEqual({
- name: 'instance_name',
- auth: {
- method: 'external',
- }
- });
- });
-});
-
-describe("instance admin api interaction with listing", () => {
-
- it("should evict cache when creating a new instance", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_LIST_INSTANCES, {
- response: {
- instances: [{
- name: 'instance_name'
- } as MerchantBackend.Instances.Instance]
- },
- });
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- const api = useAdminAPI();
- const query = useBackendInstances();
-
- return { query, api };
- },
- { wrapper: TestingContext }
- );
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
- expect(result.current.query.loading).toBeTruthy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
-
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- instances: [{
- name: 'instance_name'
- }]
- });
-
- env.addRequestExpectation(API_CREATE_INSTANCE, {
- request: {
- name: 'other_name'
- } as MerchantBackend.Instances.InstanceConfigurationMessage,
- });
-
- act(async () => {
- await result.current?.api.createInstance({
- name: 'other_name'
- } as MerchantBackend.Instances.InstanceConfigurationMessage);
- });
-
- assertJustExpectedRequestWereMade(env);
-
- env.addRequestExpectation(API_LIST_INSTANCES, {
- response: {
- instances: [{
- name: 'instance_name'
- } as MerchantBackend.Instances.Instance,
- {
- name: 'other_name'
- } as MerchantBackend.Instances.Instance]
- },
- });
-
- expect(result.current.query.loading).toBeFalsy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current.query.ok).toBeTruthy();
-
- expect(result.current.query.data).toEqual({
- instances: [{
- name: 'instance_name'
- }, {
- name: 'other_name'
- }]
- });
- });
-
- it("should evict cache when deleting an instance", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_LIST_INSTANCES, {
- response: {
- instances: [{
- id: 'default',
- name: 'instance_name'
- } as MerchantBackend.Instances.Instance,
- {
- id: 'the_id',
- name: 'second_instance'
- } as MerchantBackend.Instances.Instance]
- },
- });
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- const api = useAdminAPI();
- const query = useBackendInstances();
-
- return { query, api };
- },
- { wrapper: TestingContext }
- );
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
- expect(result.current.query.loading).toBeTruthy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
-
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- instances: [{
- id: 'default',
- name: 'instance_name'
- }, {
- id: 'the_id',
- name: 'second_instance'
- }]
- });
-
- env.addRequestExpectation(API_DELETE_INSTANCE('the_id'), {});
-
- act(async () => {
- await result.current?.api.deleteInstance('the_id');
- });
-
- assertJustExpectedRequestWereMade(env);
-
- env.addRequestExpectation(API_LIST_INSTANCES, {
- response: {
- instances: [{
- id: 'default',
- name: 'instance_name'
- } as MerchantBackend.Instances.Instance]
- },
- });
-
- expect(result.current.query.loading).toBeFalsy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current.query.ok).toBeTruthy();
-
- expect(result.current.query.data).toEqual({
- instances: [{
- id: 'default',
- name: 'instance_name'
- }]
- });
- });
- it("should evict cache when deleting (purge) an instance", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_LIST_INSTANCES, {
- response: {
- instances: [{
- id: 'default',
- name: 'instance_name'
- } as MerchantBackend.Instances.Instance,
- {
- id: 'the_id',
- name: 'second_instance'
- } as MerchantBackend.Instances.Instance]
- },
- });
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- const api = useAdminAPI();
- const query = useBackendInstances();
-
- return { query, api };
- },
- { wrapper: TestingContext }
- );
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
- expect(result.current.query.loading).toBeTruthy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
-
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- instances: [{
- id: 'default',
- name: 'instance_name'
- }, {
- id: 'the_id',
- name: 'second_instance'
- }]
- });
-
- env.addRequestExpectation(API_DELETE_INSTANCE('the_id'), {
- qparam: {
- purge: 'YES'
- }
- });
-
- act(async () => {
- await result.current?.api.purgeInstance('the_id');
- });
-
- assertJustExpectedRequestWereMade(env);
-
- env.addRequestExpectation(API_LIST_INSTANCES, {
- response: {
- instances: [{
- id: 'default',
- name: 'instance_name'
- } as MerchantBackend.Instances.Instance]
- },
- });
-
- expect(result.current.query.loading).toBeFalsy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current.query.ok).toBeTruthy();
-
- expect(result.current.query.data).toEqual({
- instances: [{
- id: 'default',
- name: 'instance_name'
- }]
- });
- });
-});
-
-describe("instance management api interaction with listing", () => {
-
- it("should evict cache when updating an instance", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_LIST_INSTANCES, {
- response: {
- instances: [{
- id: 'managed',
- name: 'instance_name'
- } as MerchantBackend.Instances.Instance]
- },
- });
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- const api = useManagementAPI('managed');
- const query = useBackendInstances();
-
- return { query, api };
- },
- { wrapper: TestingContext }
- );
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
- expect(result.current.query.loading).toBeTruthy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
-
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- instances: [{
- id: 'managed',
- name: 'instance_name'
- }]
- });
-
- env.addRequestExpectation(API_UPDATE_INSTANCE_BY_ID('managed'), {
- request: {
- name: 'other_name'
- } as MerchantBackend.Instances.InstanceReconfigurationMessage,
- });
-
- act(async () => {
- await result.current?.api.updateInstance({
- name: 'other_name'
- } as MerchantBackend.Instances.InstanceConfigurationMessage);
- });
-
- assertJustExpectedRequestWereMade(env);
-
- env.addRequestExpectation(API_LIST_INSTANCES, {
- response: {
- instances: [
- {
- id: 'managed',
- name: 'other_name'
- } as MerchantBackend.Instances.Instance]
- },
- });
-
- expect(result.current.query.loading).toBeFalsy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current.query.ok).toBeTruthy();
-
- expect(result.current.query.data).toEqual({
- instances: [{
- id: 'managed',
- name: 'other_name'
- }]
- });
- });
-
-});
-
diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/order.test.ts b/packages/merchant-backoffice-ui/tests/hooks/swr/order.test.ts
deleted file mode 100644
index dc6104e43..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/swr/order.test.ts
+++ /dev/null
@@ -1,567 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { renderHook } from "@testing-library/preact-hooks";
-import { act } from "preact/test-utils";
-import { TestingContext } from ".";
-import { MerchantBackend } from "../../../src/declaration.js";
-import { useInstanceOrders, useOrderAPI, useOrderDetails } from "../../../src/hooks/order.js";
-import {
- API_CREATE_ORDER,
- API_DELETE_ORDER,
- API_FORGET_ORDER_BY_ID,
- API_GET_ORDER_BY_ID,
- API_LIST_ORDERS, API_REFUND_ORDER_BY_ID, assertJustExpectedRequestWereMade, assertNextRequest, assertNoMoreRequestWereMade, AxiosMockEnvironment
-} from "../../axiosMock.js";
-
-describe("order api interaction with listing", () => {
-
- it("should evict cache when creating an order", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: 0, paid: "yes" },
- response: {
- orders: [{ order_id: "1" } as MerchantBackend.Orders.OrderHistoryEntry],
- },
- });
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: -20, paid: "yes" },
- response: {
- orders: [{ order_id: "2" } as MerchantBackend.Orders.OrderHistoryEntry],
- },
- });
-
-
- const { result, waitForNextUpdate } = renderHook(() => {
- const newDate = (d: Date) => {
- console.log("new date", d);
- };
- const query = useInstanceOrders({ paid: "yes" }, newDate);
- const api = useOrderAPI();
-
- return { query, api };
- }, { wrapper: TestingContext });
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
-
- expect(result.current.query.loading).toBeTruthy();
- await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- orders: [{ order_id: "1" }, { order_id: "2" }],
- });
-
- env.addRequestExpectation(API_CREATE_ORDER, {
- request: {
- order: { amount: "ARS:12", summary: "pay me" },
- },
- response: { order_id: "3" },
- });
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: 0, paid: "yes" },
- response: {
- orders: [{ order_id: "1" } as any],
- },
- });
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: -20, paid: "yes" },
- response: {
- orders: [{ order_id: "2" } as any, { order_id: "3" } as any],
- },
- });
-
- act(async () => {
- await result.current?.api.createOrder({
- order: { amount: "ARS:12", summary: "pay me" },
- } as any);
- });
-
- await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- orders: [{ order_id: "1" }, { order_id: "2" }, { order_id: "3" }],
- });
- });
- it("should evict cache when doing a refund", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: 0, paid: "yes" },
- response: {
- orders: [{ order_id: "1", amount: 'EUR:12', refundable: true } as MerchantBackend.Orders.OrderHistoryEntry],
- },
- });
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: -20, paid: "yes" },
- response: { orders: [], },
- });
-
-
- const { result, waitForNextUpdate } = renderHook(() => {
- const newDate = (d: Date) => {
- console.log("new date", d);
- };
- const query = useInstanceOrders({ paid: "yes" }, newDate);
- const api = useOrderAPI();
-
- return { query, api };
- }, { wrapper: TestingContext });
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
-
- expect(result.current.query.loading).toBeTruthy();
- await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
-
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- orders: [{
- order_id: "1",
- amount: 'EUR:12',
- refundable: true,
- }],
- });
-
- env.addRequestExpectation(API_REFUND_ORDER_BY_ID('1'), {
- request: {
- reason: 'double pay',
- refund: 'EUR:1'
- },
- });
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: 0, paid: "yes" },
- response: {
- orders: [{ order_id: "1", amount: 'EUR:12', refundable: false } as any],
- },
- });
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: -20, paid: "yes" },
- response: { orders: [], },
- });
-
- act(async () => {
- await result.current?.api.refundOrder('1', {
- reason: 'double pay',
- refund: 'EUR:1'
- });
- });
-
- await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- orders: [{
- order_id: "1",
- amount: 'EUR:12',
- refundable: false,
- }],
- });
- });
- it("should evict cache when deleting an order", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: 0, paid: "yes" },
- response: {
- orders: [{ order_id: "1" } as MerchantBackend.Orders.OrderHistoryEntry],
- },
- });
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: -20, paid: "yes" },
- response: {
- orders: [{ order_id: "2" } as MerchantBackend.Orders.OrderHistoryEntry],
- },
- });
-
-
- const { result, waitForNextUpdate } = renderHook(() => {
- const newDate = (d: Date) => {
- console.log("new date", d);
- };
- const query = useInstanceOrders({ paid: "yes" }, newDate);
- const api = useOrderAPI();
-
- return { query, api };
- }, { wrapper: TestingContext });
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
-
- expect(result.current.query.loading).toBeTruthy();
- await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- orders: [{ order_id: "1" }, { order_id: "2" }],
- });
-
- env.addRequestExpectation(API_DELETE_ORDER('1'), {});
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: 0, paid: "yes" },
- response: {
- orders: [],
- },
- });
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: -20, paid: "yes" },
- response: {
- orders: [{ order_id: "2" } as any],
- },
- });
-
- act(async () => {
- await result.current?.api.deleteOrder('1');
- });
-
- await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- orders: [{ order_id: "2" }],
- });
- });
-
-});
-
-describe("order api interaction with details", () => {
-
- it("should evict cache when doing a refund", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), {
- // qparam: { delta: 0, paid: "yes" },
- response: {
- summary: 'description',
- refund_amount: 'EUR:0',
- } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse,
- });
-
- const { result, waitForNextUpdate } = renderHook(() => {
- const query = useOrderDetails('1')
- const api = useOrderAPI();
-
- return { query, api };
- }, { wrapper: TestingContext });
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
-
- expect(result.current.query.loading).toBeTruthy();
- await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- summary: 'description',
- refund_amount: 'EUR:0',
- });
-
- env.addRequestExpectation(API_REFUND_ORDER_BY_ID('1'), {
- request: {
- reason: 'double pay',
- refund: 'EUR:1'
- },
- });
-
- env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), {
- response: {
- summary: 'description',
- refund_amount: 'EUR:1',
- } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse,
- });
-
- act(async () => {
- await result.current?.api.refundOrder('1', {
- reason: 'double pay',
- refund: 'EUR:1'
- });
- });
-
- await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- summary: 'description',
- refund_amount: 'EUR:1',
- });
- })
- it("should evict cache when doing a forget", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), {
- // qparam: { delta: 0, paid: "yes" },
- response: {
- summary: 'description',
- refund_amount: 'EUR:0',
- } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse,
- });
-
- const { result, waitForNextUpdate } = renderHook(() => {
- const query = useOrderDetails('1')
- const api = useOrderAPI();
-
- return { query, api };
- }, { wrapper: TestingContext });
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
-
- expect(result.current.query.loading).toBeTruthy();
- await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- summary: 'description',
- refund_amount: 'EUR:0',
- });
-
- env.addRequestExpectation(API_FORGET_ORDER_BY_ID('1'), {
- request: {
- fields: ['$.summary']
- },
- });
-
- env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), {
- response: {
- summary: undefined,
- } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse,
- });
-
- act(async () => {
- await result.current?.api.forgetOrder('1', {
- fields: ['$.summary']
- });
- });
-
- await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- summary: undefined,
- });
- })
-})
-
-describe("order listing pagination", () => {
-
- it("should not load more if has reach the end", async () => {
- const env = new AxiosMockEnvironment();
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: 20, wired: "yes", date_ms: 12 },
- response: {
- orders: [{ order_id: "1" } as any],
- },
- });
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: -20, wired: "yes", date_ms: 13 },
- response: {
- orders: [{ order_id: "2" } as any],
- },
- });
-
-
- const { result, waitForNextUpdate } = renderHook(() => {
- const newDate = (d: Date) => {
- console.log("new date", d);
- };
- const date = new Date(12);
- const query = useInstanceOrders({ wired: "yes", date }, newDate)
- return { query }
- }, { wrapper: TestingContext });
-
- assertJustExpectedRequestWereMade(env);
-
- await waitForNextUpdate();
-
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- orders: [{ order_id: "1" }, { order_id: "2" }],
- });
-
- expect(result.current.query.isReachingEnd).toBeTruthy()
- expect(result.current.query.isReachingStart).toBeTruthy()
-
- await act(() => {
- if (!result.current?.query.ok) throw Error("not ok");
- result.current.query.loadMore();
- });
- assertNoMoreRequestWereMade(env);
-
- await act(() => {
- if (!result.current?.query.ok) throw Error("not ok");
- result.current.query.loadMorePrev();
- });
- assertNoMoreRequestWereMade(env);
-
- expect(result.current.query.data).toEqual({
- orders: [
- { order_id: "1" },
- { order_id: "2" },
- ],
- });
- });
-
- it("should load more if result brings more that PAGE_SIZE", async () => {
- const env = new AxiosMockEnvironment();
-
- const ordersFrom0to20 = Array.from({ length: 20 }).map((e, i) => ({ order_id: String(i) }))
- const ordersFrom20to40 = Array.from({ length: 20 }).map((e, i) => ({ order_id: String(i + 20) }))
- const ordersFrom20to0 = [...ordersFrom0to20].reverse()
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: 20, wired: "yes", date_ms: 12 },
- response: {
- orders: ordersFrom0to20,
- },
- });
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: -20, wired: "yes", date_ms: 13 },
- response: {
- orders: ordersFrom20to40,
- },
- });
-
- const { result, waitForNextUpdate } = renderHook(() => {
- const newDate = (d: Date) => {
- console.log("new date", d);
- };
- const date = new Date(12);
- const query = useInstanceOrders({ wired: "yes", date }, newDate)
- return { query }
- }, { wrapper: TestingContext });
-
- assertJustExpectedRequestWereMade(env);
-
- await waitForNextUpdate({ timeout: 1 });
-
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- orders: [...ordersFrom20to0, ...ordersFrom20to40],
- });
-
- expect(result.current.query.isReachingEnd).toBeFalsy()
- expect(result.current.query.isReachingStart).toBeFalsy()
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: -40, wired: "yes", date_ms: 13 },
- response: {
- orders: [...ordersFrom20to40, { order_id: '41' }],
- },
- });
-
- await act(() => {
- if (!result.current?.query.ok) throw Error("not ok");
- result.current.query.loadMore();
- });
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- env.addRequestExpectation(API_LIST_ORDERS, {
- qparam: { delta: 40, wired: "yes", date_ms: 12 },
- response: {
- orders: [...ordersFrom0to20, { order_id: '-1' }],
- },
- });
-
- await act(() => {
- if (!result.current?.query.ok) throw Error("not ok");
- result.current.query.loadMorePrev();
- });
- await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.data).toEqual({
- orders: [{ order_id: '-1' }, ...ordersFrom20to0, ...ordersFrom20to40, { order_id: '41' }],
- });
- });
-
-
-});
diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/product.test.ts b/packages/merchant-backoffice-ui/tests/hooks/swr/product.test.ts
deleted file mode 100644
index 6e9247839..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/swr/product.test.ts
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { renderHook } from "@testing-library/preact-hooks";
-import { act } from "preact/test-utils";
-import { TestingContext } from ".";
-import { MerchantBackend } from "../../../src/declaration.js";
-import { useInstanceProducts, useProductAPI, useProductDetails } from "../../../src/hooks/product.js";
-import {
- API_CREATE_PRODUCT,
- API_DELETE_PRODUCT, API_GET_PRODUCT_BY_ID,
- API_LIST_PRODUCTS,
- API_UPDATE_PRODUCT_BY_ID,
- assertJustExpectedRequestWereMade,
- assertNextRequest,
- AxiosMockEnvironment
-} from "../../axiosMock.js";
-
-describe("product api interaction with listing", () => {
- it("should evict cache when creating a product", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_LIST_PRODUCTS, {
- response: {
- products: [{ product_id: "1234" }],
- },
- });
- env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
- response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
- });
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- const query = useInstanceProducts();
- const api = useProductAPI();
- return { api, query };
- },
- { wrapper: TestingContext }
- ); // get products -> loading
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
- expect(result.current.query.loading).toBeTruthy();
- await waitForNextUpdate({ timeout: 1 });
-
- await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual([
- { id: "1234", price: "ARS:12" },
- ]);
-
- env.addRequestExpectation(API_CREATE_PRODUCT, {
- request: { price: "ARS:23" } as MerchantBackend.Products.ProductAddDetail,
- });
-
- env.addRequestExpectation(API_LIST_PRODUCTS, {
- response: {
- products: [{ product_id: "1234" }, { product_id: "2345" }],
- },
- });
- env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
- response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
- });
- env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
- response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
- });
- env.addRequestExpectation(API_GET_PRODUCT_BY_ID("2345"), {
- response: { price: "ARS:23" } as MerchantBackend.Products.ProductDetail,
- });
-
- act(async () => {
- await result.current?.api.createProduct({
- price: "ARS:23",
- } as any);
- });
-
- assertNextRequest(env);
- await waitForNextUpdate({ timeout: 1 });
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual([
- {
- id: "1234",
- price: "ARS:12",
- },
- {
- id: "2345",
- price: "ARS:23",
- },
- ]);
- });
-
- it("should evict cache when updating a product", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_LIST_PRODUCTS, {
- response: {
- products: [{ product_id: "1234" }],
- },
- });
- env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
- response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
- });
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- const query = useInstanceProducts();
- const api = useProductAPI();
- return { api, query };
- },
- { wrapper: TestingContext }
- ); // get products -> loading
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
- expect(result.current.query.loading).toBeTruthy();
- await waitForNextUpdate({ timeout: 1 });
-
- await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual([
- { id: "1234", price: "ARS:12" },
- ]);
-
- env.addRequestExpectation(API_UPDATE_PRODUCT_BY_ID("1234"), {
- request: { price: "ARS:13" } as MerchantBackend.Products.ProductPatchDetail,
- });
-
- env.addRequestExpectation(API_LIST_PRODUCTS, {
- response: {
- products: [{ product_id: "1234" }],
- },
- });
- env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
- response: { price: "ARS:13" } as MerchantBackend.Products.ProductDetail,
- });
-
- act(async () => {
- await result.current?.api.updateProduct("1234", {
- price: "ARS:13",
- } as any);
- });
-
- assertNextRequest(env);
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual([
- {
- id: "1234",
- price: "ARS:13",
- },
- ]);
- });
-
- it("should evict cache when deleting a product", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_LIST_PRODUCTS, {
- response: {
- products: [{ product_id: "1234" }, { product_id: "2345" }],
- },
- });
- env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
- response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
- });
- env.addRequestExpectation(API_GET_PRODUCT_BY_ID("2345"), {
- response: { price: "ARS:23" } as MerchantBackend.Products.ProductDetail,
- });
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- const query = useInstanceProducts();
- const api = useProductAPI();
- return { api, query };
- },
- { wrapper: TestingContext }
- ); // get products -> loading
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
- expect(result.current.query.loading).toBeTruthy();
- await waitForNextUpdate({ timeout: 1 });
-
- await waitForNextUpdate({ timeout: 1 });
- // await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual([
- { id: "1234", price: "ARS:12" },
- { id: "2345", price: "ARS:23" },
- ]);
-
- env.addRequestExpectation(API_DELETE_PRODUCT("2345"), {});
-
- env.addRequestExpectation(API_LIST_PRODUCTS, {
- response: {
- products: [{ product_id: "1234" }],
- },
- });
- env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
- response: { price: "ARS:13" } as MerchantBackend.Products.ProductDetail,
- });
-
- act(async () => {
- await result.current?.api.deleteProduct("2345");
- });
-
- assertNextRequest(env);
- await waitForNextUpdate({ timeout: 1 });
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual([
- {
- id: "1234",
- price: "ARS:13",
- },
- ]);
- });
-
-});
-
-describe("product api interaction with details", () => {
- it("should evict cache when updating a product", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_GET_PRODUCT_BY_ID("12"), {
- response: {
- description: "this is a description",
- } as MerchantBackend.Products.ProductDetail,
- });
-
- const { result, waitForNextUpdate } = renderHook(() => {
- const query = useProductDetails("12");
- const api = useProductAPI();
- return { query, api };
- }, { wrapper: TestingContext });
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
- expect(result.current.query.loading).toBeTruthy();
- await waitForNextUpdate();
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- description: "this is a description",
- });
-
- env.addRequestExpectation(API_UPDATE_PRODUCT_BY_ID("12"), {
- request: { description: "other description" } as MerchantBackend.Products.ProductPatchDetail,
- });
-
- env.addRequestExpectation(API_GET_PRODUCT_BY_ID("12"), {
- response: {
- description: "other description",
- } as MerchantBackend.Products.ProductDetail,
- });
-
- act(async () => {
- return await result.current?.api.updateProduct("12", {
- description: "other description",
- } as any);
- });
-
- assertNextRequest(env);
- await waitForNextUpdate();
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- description: "other description",
- });
- })
-}) \ No newline at end of file
diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/reserve.test.ts b/packages/merchant-backoffice-ui/tests/hooks/swr/reserve.test.ts
deleted file mode 100644
index 8ebbee353..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/swr/reserve.test.ts
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { renderHook } from "@testing-library/preact-hooks";
-import { act } from "preact/test-utils";
-import { MerchantBackend } from "../../../src/declaration.js";
-import {
- useInstanceReserves,
- useReserveDetails,
- useReservesAPI,
- useTipDetails,
-} from "../../../src/hooks/reserves.js";
-import {
- API_AUTHORIZE_TIP,
- API_AUTHORIZE_TIP_FOR_RESERVE,
- API_CREATE_RESERVE,
- API_DELETE_RESERVE,
- API_GET_RESERVE_BY_ID,
- API_GET_TIP_BY_ID,
- API_LIST_RESERVES,
- assertJustExpectedRequestWereMade,
- AxiosMockEnvironment,
-} from "../../axiosMock.js";
-import { TestingContext } from "./index.js";
-
-describe("reserve api interaction with listing", () => {
- it("should evict cache when creating a reserve", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_LIST_RESERVES, {
- response: {
- reserves: [
- {
- reserve_pub: "11",
- } as MerchantBackend.Tips.ReserveStatusEntry,
- ],
- },
- });
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- const api = useReservesAPI();
- const query = useInstanceReserves();
-
- return { query, api };
- },
- { wrapper: TestingContext }
- );
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
- expect(result.current.query.loading).toBeTruthy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- reserves: [{ reserve_pub: "11" }],
- });
-
- env.addRequestExpectation(API_CREATE_RESERVE, {
- request: {
- initial_balance: "ARS:3333",
- exchange_url: "http://url",
- wire_method: "iban",
- },
- response: {
- reserve_pub: "22",
- payto_uri: "payto",
- },
- });
-
- act(async () => {
- await result.current?.api.createReserve({
- initial_balance: "ARS:3333",
- exchange_url: "http://url",
- wire_method: "iban",
- });
- return;
- });
-
- assertJustExpectedRequestWereMade(env);
-
- env.addRequestExpectation(API_LIST_RESERVES, {
- response: {
- reserves: [
- {
- reserve_pub: "11",
- } as MerchantBackend.Tips.ReserveStatusEntry,
- {
- reserve_pub: "22",
- } as MerchantBackend.Tips.ReserveStatusEntry,
- ],
- },
- });
-
- expect(result.current.query.loading).toBeFalsy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current.query.ok).toBeTruthy();
-
- expect(result.current.query.data).toEqual({
- reserves: [
- {
- reserve_pub: "11",
- } as MerchantBackend.Tips.ReserveStatusEntry,
- {
- reserve_pub: "22",
- } as MerchantBackend.Tips.ReserveStatusEntry,
- ],
- });
- });
-
- it("should evict cache when deleting a reserve", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_LIST_RESERVES, {
- response: {
- reserves: [
- {
- reserve_pub: "11",
- } as MerchantBackend.Tips.ReserveStatusEntry,
- {
- reserve_pub: "22",
- } as MerchantBackend.Tips.ReserveStatusEntry,
- {
- reserve_pub: "33",
- } as MerchantBackend.Tips.ReserveStatusEntry,
- ],
- },
- });
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- const api = useReservesAPI();
- const query = useInstanceReserves();
-
- return { query, api };
- },
- {
- wrapper: TestingContext,
- }
- );
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
- expect(result.current.query.loading).toBeTruthy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- reserves: [
- { reserve_pub: "11" },
- { reserve_pub: "22" },
- { reserve_pub: "33" },
- ],
- });
-
- env.addRequestExpectation(API_DELETE_RESERVE("11"), {});
-
- act(async () => {
- await result.current?.api.deleteReserve("11");
- return;
- });
-
- assertJustExpectedRequestWereMade(env);
-
- env.addRequestExpectation(API_LIST_RESERVES, {
- response: {
- reserves: [
- {
- reserve_pub: "22",
- } as MerchantBackend.Tips.ReserveStatusEntry,
- {
- reserve_pub: "33",
- } as MerchantBackend.Tips.ReserveStatusEntry,
- ],
- },
- });
-
- expect(result.current.query.loading).toBeFalsy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current.query.ok).toBeTruthy();
-
- expect(result.current.query.data).toEqual({
- reserves: [
- {
- reserve_pub: "22",
- } as MerchantBackend.Tips.ReserveStatusEntry,
- {
- reserve_pub: "33",
- } as MerchantBackend.Tips.ReserveStatusEntry,
- ],
- });
- });
-});
-
-describe("reserve api interaction with details", () => {
- it("should evict cache when adding a tip for a specific reserve", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), {
- response: {
- payto_uri: "payto://here",
- tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }],
- } as MerchantBackend.Tips.ReserveDetail,
- });
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- const api = useReservesAPI();
- const query = useReserveDetails("11");
-
- return { query, api };
- },
- {
- wrapper: TestingContext,
- }
- );
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
- expect(result.current.query.loading).toBeTruthy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- payto_uri: "payto://here",
- tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }],
- });
-
- env.addRequestExpectation(API_AUTHORIZE_TIP_FOR_RESERVE("11"), {
- request: {
- amount: "USD:12",
- justification: "not",
- next_url: "http://taler.net",
- },
- response: {
- tip_id: "id2",
- taler_tip_uri: "uri",
- tip_expiration: { t_s: 1 },
- tip_status_url: "url",
- },
- });
-
- act(async () => {
- await result.current?.api.authorizeTipReserve("11", {
- amount: "USD:12",
- justification: "not",
- next_url: "http://taler.net",
- });
- });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
-
- env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), {
- response: {
- payto_uri: "payto://here",
- tips: [
- { reason: "why?", tip_id: "id1", total_amount: "USD:10" },
- { reason: "not", tip_id: "id2", total_amount: "USD:12" },
- ],
- } as MerchantBackend.Tips.ReserveDetail,
- });
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current.query.ok).toBeTruthy();
-
- expect(result.current.query.data).toEqual({
- payto_uri: "payto://here",
- tips: [
- { reason: "why?", tip_id: "id1", total_amount: "USD:10" },
- { reason: "not", tip_id: "id2", total_amount: "USD:12" },
- ],
- });
- });
-
- it("should evict cache when adding a tip for a random reserve", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), {
- response: {
- payto_uri: "payto://here",
- tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }],
- } as MerchantBackend.Tips.ReserveDetail,
- });
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- const api = useReservesAPI();
- const query = useReserveDetails("11");
-
- return { query, api };
- },
- {
- wrapper: TestingContext,
- }
- );
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
- expect(result.current.query.loading).toBeTruthy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- payto_uri: "payto://here",
- tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }],
- });
-
- env.addRequestExpectation(API_AUTHORIZE_TIP, {
- request: {
- amount: "USD:12",
- justification: "not",
- next_url: "http://taler.net",
- },
- response: {
- tip_id: "id2",
- taler_tip_uri: "uri",
- tip_expiration: { t_s: 1 },
- tip_status_url: "url",
- },
- });
-
- act(async () => {
- await result.current?.api.authorizeTip({
- amount: "USD:12",
- justification: "not",
- next_url: "http://taler.net",
- });
- });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
-
- env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), {
- response: {
- payto_uri: "payto://here",
- tips: [
- { reason: "why?", tip_id: "id1", total_amount: "USD:10" },
- { reason: "not", tip_id: "id2", total_amount: "USD:12" },
- ],
- } as MerchantBackend.Tips.ReserveDetail,
- });
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current.query.ok).toBeTruthy();
-
- expect(result.current.query.data).toEqual({
- payto_uri: "payto://here",
- tips: [
- { reason: "why?", tip_id: "id1", total_amount: "USD:10" },
- { reason: "not", tip_id: "id2", total_amount: "USD:12" },
- ],
- });
- });
-});
-
-describe("reserve api interaction with tip details", () => {
- it("should list tips", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_GET_TIP_BY_ID("11"), {
- response: {
- total_picked_up: "USD:12",
- reason: "not",
- } as MerchantBackend.Tips.TipDetails,
- });
-
- const { result, waitForNextUpdate } = renderHook(
- () => {
- // const api = useReservesAPI();
- const query = useTipDetails("11");
-
- return { query };
- },
- {
- wrapper: TestingContext,
- }
- );
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
- expect(result.current.query.loading).toBeTruthy();
-
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- total_picked_up: "USD:12",
- reason: "not",
- });
- });
-});
diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/transfer.test.ts b/packages/merchant-backoffice-ui/tests/hooks/swr/transfer.test.ts
deleted file mode 100644
index 0b1f4a968..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/swr/transfer.test.ts
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { act, renderHook } from "@testing-library/preact-hooks";
-import { TestingContext } from "./index.js";
-import { useInstanceTransfers, useTransferAPI } from "../../../src/hooks/transfer.js";
-import {
- API_INFORM_TRANSFERS,
- API_LIST_TRANSFERS,
- assertJustExpectedRequestWereMade,
- assertNoMoreRequestWereMade,
- AxiosMockEnvironment,
-} from "../../axiosMock.js";
-import { MerchantBackend } from "../../../src/declaration.js";
-
-describe("transfer api interaction with listing", () => {
-
- it("should evict cache when informing a transfer", async () => {
- const env = new AxiosMockEnvironment();
-
- env.addRequestExpectation(API_LIST_TRANSFERS, {
- qparam: { limit: 0 },
- response: {
- transfers: [{ wtid: "2" } as MerchantBackend.Transfers.TransferDetails],
- },
- });
- // FIXME: is this query really needed? if the hook is rendered without
- // position argument then then backend is returning the newest and no need
- // to this second query
- env.addRequestExpectation(API_LIST_TRANSFERS, {
- qparam: { limit: -20 },
- response: {
- transfers: [],
- },
- });
-
- const { result, waitForNextUpdate } = renderHook(() => {
- const moveCursor = (d: string) => {
- console.log("new position", d);
- };
- const query = useInstanceTransfers({}, moveCursor);
- const api = useTransferAPI();
-
- return { query, api };
- }, { wrapper: TestingContext });
-
- expect(result.current).toBeDefined();
- if (!result.current) {
- return;
- }
-
- expect(result.current.query.loading).toBeTruthy();
- await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current.query.ok).toBeTruthy();
- if (!result.current.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- transfers: [{ wtid: "2" }],
- });
-
- env.addRequestExpectation(API_INFORM_TRANSFERS, {
- request: {
- wtid: '3',
- credit_amount: 'EUR:1',
- exchange_url: 'exchange.url',
- payto_uri: 'payto://'
- },
- response: { total: '' } as any,
- });
-
- env.addRequestExpectation(API_LIST_TRANSFERS, {
- qparam: { limit: 0 },
- response: {
- transfers: [{ wtid: "2" } as any, { wtid: "3" } as any],
- },
- });
-
- env.addRequestExpectation(API_LIST_TRANSFERS, {
- qparam: { limit: -20 },
- response: {
- transfers: [],
- },
- });
-
- act(async () => {
- await result.current?.api.informTransfer({
- wtid: '3',
- credit_amount: 'EUR:1',
- exchange_url: 'exchange.url',
- payto_uri: 'payto://'
- });
- });
-
- await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.loading).toBeFalsy();
- expect(result.current.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- transfers: [{ wtid: "3" }, { wtid: "2" }],
- });
- });
-
-});
-
-describe("transfer listing pagination", () => {
-
- it("should not load more if has reach the end", async () => {
- const env = new AxiosMockEnvironment();
- env.addRequestExpectation(API_LIST_TRANSFERS, {
- qparam: { limit: 0, payto_uri: 'payto://' },
- response: {
- transfers: [{ wtid: "2" } as any],
- },
- });
-
- env.addRequestExpectation(API_LIST_TRANSFERS, {
- qparam: { limit: -20, payto_uri: 'payto://' },
- response: {
- transfers: [{ wtid: "1" } as any],
- },
- });
-
-
- const { result, waitForNextUpdate } = renderHook(() => {
- const moveCursor = (d: string) => {
- console.log("new position", d);
- };
- const query = useInstanceTransfers({ payto_uri: 'payto://' }, moveCursor)
- return { query }
- }, { wrapper: TestingContext });
-
- assertJustExpectedRequestWereMade(env);
-
- await waitForNextUpdate();
-
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- transfers: [{ wtid: "2" }, { wtid: "1" }],
- });
-
- expect(result.current.query.isReachingEnd).toBeTruthy()
- expect(result.current.query.isReachingStart).toBeTruthy()
-
- await act(() => {
- if (!result.current?.query.ok) throw Error("not ok");
- result.current.query.loadMore();
- });
- assertNoMoreRequestWereMade(env);
-
- await act(() => {
- if (!result.current?.query.ok) throw Error("not ok");
- result.current.query.loadMorePrev();
- });
- assertNoMoreRequestWereMade(env);
-
- expect(result.current.query.data).toEqual({
- transfers: [
- { wtid: "2" },
- { wtid: "1" },
- ],
- });
- });
-
- it("should load more if result brings more that PAGE_SIZE", async () => {
- const env = new AxiosMockEnvironment();
-
- const transfersFrom0to20 = Array.from({ length: 20 }).map((e, i) => ({ wtid: String(i) }))
- const transfersFrom20to40 = Array.from({ length: 20 }).map((e, i) => ({ wtid: String(i + 20) }))
- const transfersFrom20to0 = [...transfersFrom0to20].reverse()
-
- env.addRequestExpectation(API_LIST_TRANSFERS, {
- qparam: { limit: 20, payto_uri: 'payto://' },
- response: {
- transfers: transfersFrom0to20,
- },
- });
-
- env.addRequestExpectation(API_LIST_TRANSFERS, {
- qparam: { limit: -20, payto_uri: 'payto://' },
- response: {
- transfers: transfersFrom20to40,
- },
- });
-
- const { result, waitForNextUpdate } = renderHook(() => {
- const moveCursor = (d: string) => {
- console.log("new position", d);
- };
- const query = useInstanceTransfers({ payto_uri: 'payto://', position: '1' }, moveCursor)
- return { query }
- }, { wrapper: TestingContext });
-
- assertJustExpectedRequestWereMade(env);
-
- await waitForNextUpdate({ timeout: 1 });
-
- expect(result.current?.query.ok).toBeTruthy();
- if (!result.current?.query.ok) return;
-
- expect(result.current.query.data).toEqual({
- transfers: [...transfersFrom20to0, ...transfersFrom20to40],
- });
-
- expect(result.current.query.isReachingEnd).toBeFalsy()
- expect(result.current.query.isReachingStart).toBeFalsy()
-
- env.addRequestExpectation(API_LIST_TRANSFERS, {
- qparam: { limit: -40, payto_uri: 'payto://', offset: "1" },
- response: {
- transfers: [...transfersFrom20to40, { wtid: '41' }],
- },
- });
-
- await act(() => {
- if (!result.current?.query.ok) throw Error("not ok");
- result.current.query.loadMore();
- });
- await waitForNextUpdate({ timeout: 1 });
-
- assertJustExpectedRequestWereMade(env);
-
- env.addRequestExpectation(API_LIST_TRANSFERS, {
- qparam: { limit: 40, payto_uri: 'payto://', offset: "1" },
- response: {
- transfers: [...transfersFrom0to20, { wtid: '-1' }],
- },
- });
-
- await act(() => {
- if (!result.current?.query.ok) throw Error("not ok");
- result.current.query.loadMorePrev();
- });
- await waitForNextUpdate({ timeout: 1 });
- assertJustExpectedRequestWereMade(env);
-
- expect(result.current.query.data).toEqual({
- transfers: [{ wtid: '-1' }, ...transfersFrom20to0, ...transfersFrom20to40, { wtid: '41' }],
- });
- });
-
-
-});