gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]