[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant-backoffice] 01/03: more unit test and kyc interfaces
From: |
gnunet |
Subject: |
[taler-merchant-backoffice] 01/03: more unit test and kyc interfaces |
Date: |
Thu, 16 Dec 2021 20:20:45 +0100 |
This is an automated email from the git hooks/post-receive script.
sebasjm pushed a commit to branch master
in repository merchant-backoffice.
commit 5365512386a26e4f48ac50d3e99ae855ce0c75fa
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Thu Dec 16 08:56:03 2021 -0300
more unit test and kyc interfaces
---
packages/merchant-backoffice/src/declaration.d.ts | 40 ++
packages/merchant-backoffice/src/hooks/admin.ts | 63 --
packages/merchant-backoffice/src/hooks/instance.ts | 87 ++-
.../src/paths/admin/create/index.tsx | 2 +-
.../src/paths/admin/list/index.tsx | 175 +++---
packages/merchant-backoffice/tests/axiosMock.ts | 102 ++++
.../merchant-backoffice/tests/hooks/async.test.ts | 158 +++++
.../tests/hooks/swr/instance.test.ts | 636 +++++++++++++++++++++
8 files changed, 1093 insertions(+), 170 deletions(-)
diff --git a/packages/merchant-backoffice/src/declaration.d.ts
b/packages/merchant-backoffice/src/declaration.d.ts
index e5486de..35b80c6 100644
--- a/packages/merchant-backoffice/src/declaration.d.ts
+++ b/packages/merchant-backoffice/src/declaration.d.ts
@@ -388,6 +388,45 @@ export namespace MerchantBackend {
}
+ //GET /private/instances/$INSTANCE/kyc
+ interface AccountKycRedirects {
+ // Array of pending KYCs.
+ pending_kycs: MerchantAccountKycRedirect[];
+
+ // Array of exchanges with no reply.
+ timeout_kycs: ExchangeKycTimeout[];
+
+ }
+ interface MerchantAccountKycRedirect {
+
+ // URL that the user should open in a browser to
+ // proceed with the KYC process (as returned
+ // by the exchange's /kyc-check/ endpoint).
+ kyc_url: string;
+
+ // Base URL of the exchange this is about.
+ exchange_url: string;
+
+ // Our bank wire account this is about.
+ payto_uri: string;
+
+ }
+ interface ExchangeKycTimeout {
+
+ // Base URL of the exchange this is about.
+ exchange_url: string;
+
+ // Numeric error code indicating errors the exchange
+ // returned, or TALER_EC_INVALID for none.
+ exchange_code: number;
+
+ // HTTP status code returned by the exchange when we asked for
+ // information about the KYC status.
+ // 0 if there was no response at all.
+ exchange_http_status: number;
+
+ }
+
//GET /private/instances/$INSTANCE
interface QueryInstancesResponse {
// The URI where the wallet will send coins. A merchant may have
@@ -433,6 +472,7 @@ export namespace MerchantBackend {
// Does not contain the token when token auth is configured.
auth: {
method: "external" | "token";
+ token?: string;
};
}
diff --git a/packages/merchant-backoffice/src/hooks/admin.ts
b/packages/merchant-backoffice/src/hooks/admin.ts
deleted file mode 100644
index 1ac9e69..0000000
--- a/packages/merchant-backoffice/src/hooks/admin.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021 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 { MerchantBackend } from "../declaration";
-import { useBackendContext } from "../context/backend";
-import { request, useMatchMutate } from "./backend";
-
-export function useAdminAPI(): AdminAPI {
- const { url, token } = useBackendContext();
- const mutateAll = useMatchMutate();
-
- const createInstance = async (
- instance: MerchantBackend.Instances.InstanceConfigurationMessage
- ): Promise<void> => {
- await request(`${url}/management/instances`, {
- method: "post",
- token,
- data: instance,
- });
-
- mutateAll(/@"\/private\/instances"@/);
- };
-
- const deleteInstance = async (id: string): Promise<void> => {
- await request(`${url}/management/instances/${id}`, {
- method: "delete",
- token,
- });
-
- mutateAll(/@"\/private\/instances"@/);
- };
-
- const purgeInstance = async (id: string): Promise<void> => {
- await request(`${url}/management/instances/${id}?purge=YES`, {
- method: "delete",
- token,
- });
-
- mutateAll(/@"\/private\/instances"@/);
- };
-
- return { createInstance, deleteInstance, purgeInstance };
-}
-
-export interface AdminAPI {
- createInstance: (
- data: MerchantBackend.Instances.InstanceConfigurationMessage
- ) => Promise<void>;
- deleteInstance: (id: string) => Promise<void>;
- purgeInstance: (id: string) => Promise<void>;
-}
diff --git a/packages/merchant-backoffice/src/hooks/instance.ts
b/packages/merchant-backoffice/src/hooks/instance.ts
index 2f4923e..995d055 100644
--- a/packages/merchant-backoffice/src/hooks/instance.ts
+++ b/packages/merchant-backoffice/src/hooks/instance.ts
@@ -13,18 +13,17 @@
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 { MerchantBackend } from "../declaration";
+import useSWR, { useSWRConfig } from "swr";
import { useBackendContext } from "../context/backend";
+import { useInstanceContext } from "../context/instance";
+import { MerchantBackend } from "../declaration";
import {
fetcher,
HttpError,
HttpResponse,
HttpResponseOk,
- request,
- SwrError,
+ request, useMatchMutate
} from "./backend";
-import useSWR, { useSWRConfig } from "swr";
-import { useInstanceContext } from "../context/instance";
interface InstanceAPI {
updateInstance: (
@@ -35,8 +34,56 @@ interface InstanceAPI {
setNewToken: (token: string) => Promise<void>;
}
+export function useAdminAPI(): AdminAPI {
+ const { url, token } = useBackendContext();
+ const mutateAll = useMatchMutate();
+
+ const createInstance = async (
+ instance: MerchantBackend.Instances.InstanceConfigurationMessage
+ ): Promise<void> => {
+ await request(`${url}/management/instances`, {
+ method: "post",
+ token,
+ data: instance,
+ });
+
+ mutateAll(/\/management\/instances/);
+ };
+
+ const deleteInstance = async (id: string): Promise<void> => {
+ await request(`${url}/management/instances/${id}`, {
+ method: "delete",
+ token,
+ });
+
+ mutateAll(/\/management\/instances/);
+ };
+
+ const purgeInstance = async (id: string): Promise<void> => {
+ await request(`${url}/management/instances/${id}`, {
+ method: "delete",
+ token,
+ params: {
+ purge: 'YES'
+ }
+ });
+
+ mutateAll(/\/management\/instances/);
+ };
+
+ return { createInstance, deleteInstance, purgeInstance };
+}
+
+export interface AdminAPI {
+ createInstance: (
+ data: MerchantBackend.Instances.InstanceConfigurationMessage
+ ) => Promise<void>;
+ deleteInstance: (id: string) => Promise<void>;
+ purgeInstance: (id: string) => Promise<void>;
+}
+
export function useManagementAPI(instanceId: string): InstanceAPI {
- const { mutate } = useSWRConfig();
+ const mutateAll = useMatchMutate();
const { url, token } = useBackendContext();
const updateInstance = async (
@@ -48,7 +95,7 @@ export function useManagementAPI(instanceId: string):
InstanceAPI {
data: instance,
});
- mutate([`/private/`, token, url], null);
+ mutateAll(/\/management\/instances/);
};
const deleteInstance = async (): Promise<void> => {
@@ -57,7 +104,7 @@ export function useManagementAPI(instanceId: string):
InstanceAPI {
token,
});
- mutate([`/private/`, token, url], null);
+ mutateAll(/\/management\/instances/);
};
const clearToken = async (): Promise<void> => {
@@ -67,7 +114,7 @@ export function useManagementAPI(instanceId: string):
InstanceAPI {
data: { method: "external" },
});
- mutate([`/private/`, token, url], null);
+ mutateAll(/\/management\/instances/);
};
const setNewToken = async (newToken: string): Promise<void> => {
@@ -77,7 +124,7 @@ export function useManagementAPI(instanceId: string):
InstanceAPI {
data: { method: "token", token: newToken },
});
- mutate([`/private/`, token, url], null);
+ mutateAll(/\/management\/instances/);
};
return { updateInstance, deleteInstance, setNewToken, clearToken };
@@ -89,14 +136,8 @@ export function useInstanceAPI(): InstanceAPI {
const { token: instanceToken, id, admin } = useInstanceContext();
const { url, token } = !admin
- ? {
- url: baseUrl,
- token: adminToken,
- }
- : {
- url: `${baseUrl}/instances/${id}`,
- token: instanceToken,
- };
+ ? { url: baseUrl, token: adminToken, }
+ : { url: `${baseUrl}/instances/${id}`, token: instanceToken, };
const updateInstance = async (
instance: MerchantBackend.Instances.InstanceReconfigurationMessage
@@ -149,14 +190,8 @@ export function useInstanceDetails():
HttpResponse<MerchantBackend.Instances.Que
const { token: instanceToken, id, admin } = useInstanceContext();
const { url, token } = !admin
- ? {
- url: baseUrl,
- token: baseToken,
- }
- : {
- url: `${baseUrl}/instances/${id}`,
- token: instanceToken,
- };
+ ? { url: baseUrl, token: baseToken, }
+ : { url: `${baseUrl}/instances/${id}`, token: instanceToken, };
const { data, error, isValidating } = useSWR<
HttpResponseOk<MerchantBackend.Instances.QueryInstancesResponse>,
diff --git a/packages/merchant-backoffice/src/paths/admin/create/index.tsx
b/packages/merchant-backoffice/src/paths/admin/create/index.tsx
index 3f31b3d..aaed6d6 100644
--- a/packages/merchant-backoffice/src/paths/admin/create/index.tsx
+++ b/packages/merchant-backoffice/src/paths/admin/create/index.tsx
@@ -21,7 +21,7 @@ import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { NotificationCard } from "../../../components/menu";
import { MerchantBackend } from "../../../declaration";
-import { useAdminAPI } from "../../../hooks/admin";
+import { useAdminAPI } from "../../../hooks/instance";
import { useTranslator } from "../../../i18n";
import { Notification } from "../../../utils/types";
import { CreatePage } from "./CreatePage";
diff --git a/packages/merchant-backoffice/src/paths/admin/list/index.tsx
b/packages/merchant-backoffice/src/paths/admin/list/index.tsx
index f762a07..c5609fd 100644
--- a/packages/merchant-backoffice/src/paths/admin/list/index.tsx
+++ b/packages/merchant-backoffice/src/paths/admin/list/index.tsx
@@ -15,22 +15,21 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
-import { Fragment, h, VNode } from 'preact';
-import { useState } from 'preact/hooks';
-import { Loading } from '../../../components/exception/loading';
-import { NotificationCard } from '../../../components/menu';
-import { DeleteModal, PurgeModal } from '../../../components/modal';
-import { MerchantBackend } from '../../../declaration';
-import { useAdminAPI } from "../../../hooks/admin";
-import { HttpError } from '../../../hooks/backend';
-import { useBackendInstances } from '../../../hooks/instance';
-import { useTranslator } from '../../../i18n';
-import { Notification } from '../../../utils/types';
-import { View } from './View';
+import { Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { Loading } from "../../../components/exception/loading";
+import { NotificationCard } from "../../../components/menu";
+import { DeleteModal, PurgeModal } from "../../../components/modal";
+import { MerchantBackend } from "../../../declaration";
+import { HttpError } from "../../../hooks/backend";
+import { useAdminAPI, useBackendInstances } from "../../../hooks/instance";
+import { useTranslator } from "../../../i18n";
+import { Notification } from "../../../utils/types";
+import { View } from "./View";
interface Props {
onCreate: () => void;
@@ -39,73 +38,89 @@ interface Props {
onUnauthorized: () => VNode;
onNotFound: () => VNode;
onLoadError: (error: HttpError) => VNode;
- setInstanceName: (s:string) => void;
+ setInstanceName: (s: string) => void;
}
-export default function Instances({ onUnauthorized, onLoadError, onNotFound,
onCreate, onUpdate, setInstanceName }: Props): VNode {
- const result = useBackendInstances()
- const [deleting, setDeleting] = useState<MerchantBackend.Instances.Instance
| null>(null)
- const [purging, setPurging] = useState<MerchantBackend.Instances.Instance |
null>(null)
- const { deleteInstance, purgeInstance } = useAdminAPI()
- const [notif, setNotif] = useState<Notification | undefined>(undefined)
- const i18n = useTranslator()
+export default function Instances({
+ onUnauthorized,
+ onLoadError,
+ onNotFound,
+ onCreate,
+ onUpdate,
+ setInstanceName,
+}: Props): VNode {
+ const result = useBackendInstances();
+ const [deleting, setDeleting] =
+ useState<MerchantBackend.Instances.Instance | null>(null);
+ const [purging, setPurging] =
+ useState<MerchantBackend.Instances.Instance | null>(null);
+ const { deleteInstance, purgeInstance } = useAdminAPI();
+ const [notif, setNotif] = useState<Notification | undefined>(undefined);
+ const i18n = useTranslator();
- if (result.clientError && result.isUnauthorized) return onUnauthorized()
- if (result.clientError && result.isNotfound) return onNotFound()
- if (result.loading) return <Loading />
- if (!result.ok) return onLoadError(result)
+ if (result.clientError && result.isUnauthorized) return onUnauthorized();
+ if (result.clientError && result.isNotfound) return onNotFound();
+ if (result.loading) return <Loading />;
+ if (!result.ok) return onLoadError(result);
- return <Fragment>
- <NotificationCard notification={notif} />
- <View instances={result.data.instances}
- onDelete={setDeleting}
- onCreate={onCreate}
- onPurge={setPurging}
- onUpdate={onUpdate}
- setInstanceName={setInstanceName}
- selected={!!deleting}
- />
- {deleting && <DeleteModal
- element={deleting}
- onCancel={() => setDeleting(null)}
- onConfirm={async (): Promise<void> => {
- try {
- await deleteInstance(deleting.id)
- // pushNotification({ message: 'delete_success', type: 'SUCCESS' })
- setNotif({
- message: i18n`Instance "${deleting.name}" (ID: ${deleting.id}) has
been deleted`,
- type: 'SUCCESS'
- })
- } catch (error) {
- setNotif({
- message: i18n`Failed to delete instance`,
- type: "ERROR",
- description: error instanceof Error ? error.message : undefined
- })
- // pushNotification({ message: 'delete_error', type: 'ERROR' })
- }
- setDeleting(null)
- }}
- />}
- {purging && <PurgeModal
- element={purging}
- onCancel={() => setPurging(null)}
- onConfirm={async (): Promise<void> => {
- try {
- await purgeInstance(purging.id)
- setNotif({
- message: i18n`Instance "${purging.name}" (ID: ${purging.id}) has
been disabled`,
- type: 'SUCCESS'
- })
- } catch (error) {
- setNotif({
- message: i18n`Failed to purge instance`,
- type: "ERROR",
- description: error instanceof Error ? error.message : undefined
- })
- }
- setPurging(null)
- }}
- />}
- </Fragment>;
+ return (
+ <Fragment>
+ <NotificationCard notification={notif} />
+ <View
+ instances={result.data.instances}
+ onDelete={setDeleting}
+ onCreate={onCreate}
+ onPurge={setPurging}
+ onUpdate={onUpdate}
+ setInstanceName={setInstanceName}
+ selected={!!deleting}
+ />
+ {deleting && (
+ <DeleteModal
+ element={deleting}
+ onCancel={() => setDeleting(null)}
+ onConfirm={async (): Promise<void> => {
+ try {
+ await deleteInstance(deleting.id);
+ // pushNotification({ message: 'delete_success', type: 'SUCCESS'
})
+ setNotif({
+ message: i18n`Instance "${deleting.name}" (ID: ${deleting.id})
has been deleted`,
+ type: "SUCCESS",
+ });
+ } catch (error) {
+ setNotif({
+ message: i18n`Failed to delete instance`,
+ type: "ERROR",
+ description: error instanceof Error ? error.message :
undefined,
+ });
+ // pushNotification({ message: 'delete_error', type: 'ERROR' })
+ }
+ setDeleting(null);
+ }}
+ />
+ )}
+ {purging && (
+ <PurgeModal
+ element={purging}
+ onCancel={() => setPurging(null)}
+ onConfirm={async (): Promise<void> => {
+ try {
+ await purgeInstance(purging.id);
+ setNotif({
+ message: i18n`Instance "${purging.name}" (ID: ${purging.id})
has been disabled`,
+ type: "SUCCESS",
+ });
+ } catch (error) {
+ setNotif({
+ message: i18n`Failed to purge instance`,
+ type: "ERROR",
+ description: error instanceof Error ? error.message :
undefined,
+ });
+ }
+ setPurging(null);
+ }}
+ />
+ )}
+ </Fragment>
+ );
}
diff --git a/packages/merchant-backoffice/tests/axiosMock.ts
b/packages/merchant-backoffice/tests/axiosMock.ts
index 7b33e2a..412d2a0 100644
--- a/packages/merchant-backoffice/tests/axiosMock.ts
+++ b/packages/merchant-backoffice/tests/axiosMock.ts
@@ -330,3 +330,105 @@ export const API_DELETE_RESERVE = (
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/tests/hooks/async.test.ts
b/packages/merchant-backoffice/tests/hooks/async.test.ts
new file mode 100644
index 0000000..a6d0cdd
--- /dev/null
+++ b/packages/merchant-backoffice/tests/hooks/async.test.ts
@@ -0,0 +1,158 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 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"
+
+/**
+*
+* @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/tests/hooks/swr/instance.test.ts
b/packages/merchant-backoffice/tests/hooks/swr/instance.test.ts
new file mode 100644
index 0000000..55d9fa6
--- /dev/null
+++ b/packages/merchant-backoffice/tests/hooks/swr/instance.test.ts
@@ -0,0 +1,636 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 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";
+import { useAdminAPI, useBackendInstances, useInstanceAPI, useInstanceDetails,
useManagementAPI } from "../../../src/hooks/instance";
+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";
+import { TestingContext } from "./index";
+
+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 }
+ );
+
+ if (!result.current) {
+ expect(result.current).toBeDefined();
+ 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 }
+ );
+
+ if (!result.current) {
+ expect(result.current).toBeDefined();
+ 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 }
+ );
+
+ if (!result.current) {
+ expect(result.current).toBeDefined();
+ 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 }
+ );
+
+ if (!result.current) {
+ expect(result.current).toBeDefined();
+ 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 }
+ );
+
+ if (!result.current) {
+ expect(result.current).toBeDefined();
+ 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 }
+ );
+
+ if (!result.current) {
+ expect(result.current).toBeDefined();
+ 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 }
+ );
+
+ if (!result.current) {
+ expect(result.current).toBeDefined();
+ 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'
+ }]
+ });
+ });
+
+});
+
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.