Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

explorer: Add service method to check for stale market data #1920

Merged
merged 1 commit into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 84 additions & 3 deletions explorer/src/lib/stores/__tests__/marketDataStore.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ const { fakeMarketDataA, settleTime } = vi.hoisted(() => ({
settleTime: 1000,
}));

vi.mock("svelte/store", async (importOriginal) => {
/** @type {import("svelte/store")} */
const original = await importOriginal();

return {
...original,
get: vi.fn((store) => original.get(store)),
};
});

vi.mock("$lib/services", async (importOriginal) => ({
.../** @type {import("$lib/services")} */ (await importOriginal()),
duskAPI: {
Expand All @@ -26,22 +36,26 @@ describe("marketDataStore", async () => {
const { marketDataFetchInterval } = get(appStore);
const fakeMarketDataB = { data: "B" };

/** @type {MarketDataStore} */
let marketDataStore;

vi.useFakeTimers();

beforeEach(async () => {
vi.resetModules();
vi.clearAllTimers();
vi.mocked(duskAPI.getMarketData).mockClear();

marketDataStore = (await import("../marketDataStore")).default;
});

afterAll(() => {
vi.doUnmock("$lib/services");
vi.doUnmock("svelte/store");
vi.useRealTimers();
});

it("should start polling for market data and update the `lastUpdate` property when data changes", async () => {
const marketDataStore = (await import("../marketDataStore")).default;

/**
* This is the result for the second call as the first one
* starts with the import and isn't resolved yet
Expand Down Expand Up @@ -96,7 +110,6 @@ describe("marketDataStore", async () => {
});

it("should not reset its data and continue polling after an error, without resetting it as well", async () => {
const marketDataStore = (await import("../marketDataStore")).default;
const error = new Error("Some error message");

/**
Expand Down Expand Up @@ -149,4 +162,72 @@ describe("marketDataStore", async () => {
lastUpdate: new Date(),
});
});

describe("Stale data checks", () => {
const startingStore = {
data: null,
error: null,
isLoading: false,
lastUpdate: null,
};
const storeWithData = {
...startingStore,
data: fakeMarketDataA,
lastUpdate: new Date(),
};

it("should not consider data as stale if there's no data", () => {
vi.mocked(get).mockReturnValueOnce(startingStore);

expect(marketDataStore.isDataStale()).toBe(false);
});

it("should not consider data as stale if the store is loading and there is no error, even if the last update exceeds the fetch interval", () => {
vi.mocked(get)
.mockReturnValueOnce({ ...startingStore, isLoading: true })
.mockReturnValueOnce({ ...storeWithData, isLoading: true })
.mockReturnValueOnce({
...storeWithData,
isLoading: true,
lastUpdate: new Date(Date.now() - marketDataFetchInterval - 1),
});

expect(marketDataStore.isDataStale()).toBe(false);
expect(marketDataStore.isDataStale()).toBe(false);
expect(marketDataStore.isDataStale()).toBe(false);
});

it("should consider data as stale if there's an error and data, even if the store is loading", () => {
const storeWithError = {
...storeWithData,
error: new Error("some error"),
};

vi.mocked(get)
.mockReturnValueOnce(storeWithError)
.mockReturnValueOnce({ ...storeWithError, isLoading: true })
.mockReturnValueOnce({ ...storeWithError, lastUpdate: null })
.mockReturnValueOnce({ ...storeWithError, error: null });

expect(marketDataStore.isDataStale()).toBe(true);
expect(marketDataStore.isDataStale()).toBe(true);
expect(marketDataStore.isDataStale()).toBe(false);
expect(marketDataStore.isDataStale()).toBe(false);
});

it("should consider data as stale if the last update exceeds the fetch interval", () => {
vi.mocked(get)
.mockReturnValueOnce({
...storeWithData,
lastUpdate: new Date(Date.now() - marketDataFetchInterval - 1),
})
.mockReturnValueOnce({
...storeWithData,
lastUpdate: new Date(Date.now() - marketDataFetchInterval),
});

expect(marketDataStore.isDataStale()).toBe(true);
expect(marketDataStore.isDataStale()).toBe(false);
});
});
});
10 changes: 10 additions & 0 deletions explorer/src/lib/stores/marketDataStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,19 @@ const marketDataStore = derived(
initialState
);

function isDataStale() {
const { error, isLoading, lastUpdate } = get(marketDataStore);

return (
!!lastUpdate &&
(error !== null || (!isLoading && Date.now() > +lastUpdate + fetchInterval))
);
}

ascartabelli marked this conversation as resolved.
Show resolved Hide resolved
pollingDataStore.start();

/** @type {MarketDataStore} */
export default {
isDataStale,
subscribe: marketDataStore.subscribe,
};
5 changes: 4 additions & 1 deletion explorer/src/lib/stores/stores.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ type AppStoreContent = {
transactionsListEntries: number;
};

type MarketDataStore = import("svelte/store").Readable<MarketDataStoreContent>;
type MarketDataStore =
import("svelte/store").Readable<MarketDataStoreContent> & {
isDataStale: () => boolean;
};

type MarketDataStoreContent = {
data: MarketData | null;
Expand Down
Loading