added web account context test

This commit is contained in:
balzack 2023-01-12 06:01:32 -08:00
parent e068273487
commit 132d7cc003
6 changed files with 164 additions and 31 deletions

View File

@ -7,14 +7,16 @@ import { StoreContext } from './StoreContext';
export function useAccountContext() { export function useAccountContext() {
const [state, setState] = useState({ const [state, setState] = useState({
init: false, offsync: false,
status: null, status: null,
seal: null, seal: null,
sealPrivate: null, sealKey: null,
}); });
const access = useRef(null); const access = useRef(null);
const revision = useRef(null); const setRevision = useRef(null);
const next = useRef(null); const curRevision = useRef(null);
const syncing = useRef(false);
const force = useRef(false);
const storeContext = useContext(StoreContext); const storeContext = useContext(StoreContext);
@ -24,37 +26,48 @@ export function useAccountContext() {
useEffect(() => { useEffect(() => {
updateState({ sealKey: storeContext.state.sealKey }); updateState({ sealKey: storeContext.state.sealKey });
}, [storeContext.state.sealKey]); }, [storeContext.state]);
const setStatus = async (rev) => { const sync = async () => {
if (next.current == null) { if (!syncing.current && (setRevision.current !== curRevision.current || force.current)) {
if (revision.current !== rev) { syncing.current = true;
let status = await getAccountStatus(access.current); force.current = false;
updateState({ init: true, status, seal: status.seal });
revision.current = rev; try {
const token = access.current;
const revision = curRevision.current;
const status = await getAccountStatus(token);
setRevision.current = revision;
updateState({ offsync: false, status, seal: status.seal });
} }
if (next.current != null) { catch (err) {
let r = next.current; console.log(err);
next.current = null; syncing.current = false;
setStatus(r); updateState({ offsync: true });
return;
} }
}
else { syncing.current = false;
next.current = rev; await sync();
} }
} }
const actions = { const actions = {
setToken: (token) => { setToken: (token) => {
if (access.current || syncing.current) {
throw new Error("invalid account session state");
}
access.current = token; access.current = token;
curRevision.current = null;
setRevision.current = null;
setState({ offsync: false, status: null, seal: null, sealKey: null });
}, },
clearToken: () => { clearToken: () => {
access.current = null; access.current = null;
revision.current = 0;
setState({ init: false, seal: {}, sealKey: {} });
}, },
setRevision: async (rev) => { setRevision: async (rev) => {
setStatus(rev); curRevision.current = rev;
await sync();
}, },
setSearchable: async (flag) => { setSearchable: async (flag) => {
await setAccountSearchable(access.current, flag); await setAccountSearchable(access.current, flag);
@ -74,6 +87,10 @@ export function useAccountContext() {
setLogin: async (username, password) => { setLogin: async (username, password) => {
await setAccountLogin(access.current, username, password); await setAccountLogin(access.current, username, password);
}, },
resync: async () => {
force.current = true;
await sync();
},
} }
return { state, actions } return { state, actions }

View File

@ -38,10 +38,19 @@ export function useAppContext(websocket) {
const cardContext = useContext(CardContext); const cardContext = useContext(CardContext);
const setSession = (token) => { const setSession = (token) => {
accountContext.actions.setToken(token); try {
profileContext.actions.setToken(token); accountContext.actions.setToken(token);
cardContext.actions.setToken(token); profileContext.actions.setToken(token);
channelContext.actions.setToken(token); cardContext.actions.setToken(token);
channelContext.actions.setToken(token);
}
catch (err) {
accountContext.actions.clearToken();
profileContext.actions.clearToken();
cardContext.actions.clearToken();
channelContext.actions.clearToken();
throw err;
}
setWebsocket(token); setWebsocket(token);
} }

View File

@ -0,0 +1,112 @@
import React, { useState, useEffect, useContext } from 'react';
import {render, act, screen, waitFor, fireEvent} from '@testing-library/react'
import { AccountContextProvider, AccountContext } from 'context/AccountContext';
import { StoreContextProvider } from 'context/StoreContext';
import * as fetchUtil from 'api/fetchUtil';
let accountContext = null;
function AccountView() {
const [renderCount, setRenderCount] = useState(0);
const account = useContext(AccountContext);
accountContext = account;
useEffect(() => {
setRenderCount(renderCount + 1);
}, [account.state]);
return (
<div>
<span data-testid="count">{ renderCount }</span>
<span data-testid="seal">{ account.state.seal }</span>
<span data-testid="sealKey">{ account.state.sealKey }</span>
<span data-testid="searchable">{ account.state.status?.searchable.toString() }</span>
</div>
);
}
function AccountTestApp() {
return (
<StoreContextProvider>
<AccountContextProvider>
<AccountView />
</AccountContextProvider>
</StoreContextProvider>
)
}
const realFetchWithTimeout = fetchUtil.fetchWithTimeout;
const realFetchWithCustomTimeout = fetchUtil.fetchWithCustomTimeout;
let fetchStatus;
let sealSet;
beforeEach(() => {
fetchStatus = {};
sealSet = false;
const mockFetch = jest.fn().mockImplementation((url, options) => {
if (url === '/account/seal?agent=abc123') {
sealSet = true;
return Promise.resolve({
json: () => Promise.resolve({})
});
}
else if (url === '/account/status?agent=abc123') {
return Promise.resolve({
json: () => Promise.resolve(fetchStatus)
});
}
else {
return Promise.resolve({
json: () => Promise.resolve({})
});
}
});
fetchUtil.fetchWithTimeout = mockFetch;
fetchUtil.fetchWithCustomTimeout = mockFetch;
});
afterEach(() => {
fetchUtil.fetchWithTimeout = realFetchWithTimeout;
fetchUtil.fetchWithCustomTimeout = realFetchWithCustomTimeout;
});
test('testing account sync', async () => {
render(<AccountTestApp />);
await waitFor(async () => {
expect(accountContext).not.toBe(null);
});
fetchStatus = { disabled: false, storageUsed: 1, searchable: true, pushEnabled: false, sealable: true };
await act(async () => {
accountContext.actions.setToken('abc123');
await accountContext.actions.setRevision(1);
});
await waitFor(async () => {
expect(screen.getByTestId('searchable').textContent).toBe('true');
expect(screen.getByTestId('seal').textContent).toBe('');
expect(screen.getByTestId('sealKey').textContent).toBe('');
expect(sealSet).toBe(false);
});
await act(async () => {
await accountContext.actions.setSeal('testeal', 'testsealkey');
});
fetchStatus = { disabled: false, storageUsed: 1, searchable: true, pushEnabled: false, sealable: true, seal: 'testseal' };
await act(async () => {
await accountContext.actions.setRevision(2);
});
await waitFor(async () => {
expect(sealSet).toBe(true);
expect(screen.getByTestId('seal').textContent).toBe('testseal');
expect(screen.getByTestId('sealKey').textContent).toBe('testsealkey');
});
});

View File

@ -68,8 +68,6 @@ beforeEach(() => {
}); });
const mockFetch = jest.fn().mockImplementation((url, options) => { const mockFetch = jest.fn().mockImplementation((url, options) => {
console.log(url, options);
const params = url.split('/'); const params = url.split('/');
if (params[1] === 'account' && options.method === 'POST') { if (params[1] === 'account' && options.method === 'POST') {
return Promise.resolve({ return Promise.resolve({
@ -119,7 +117,7 @@ test('testing app sync', async () => {
}); });
await act(async () => { await act(async () => {
mockWebsocket.onclose(); mockWebsocket.onclose('test close');
await new Promise(r => setTimeout(r, 1000)); await new Promise(r => setTimeout(r, 1000));
}); });

View File

@ -96,8 +96,6 @@ beforeEach(() => {
}); });
} }
else { else {
console.log(params, options);
return Promise.resolve({ return Promise.resolve({
url: 'endpoint', url: 'endpoint',
status: 200, status: 200,

View File

@ -131,7 +131,6 @@ beforeEach(() => {
}); });
} }
else { else {
console.log(params, options);
return Promise.resolve({ return Promise.resolve({
url: 'endpoint', url: 'endpoint',
status: 200, status: 200,