Skip to content

Commit 46d8b35

Browse files
fixup! feat: asset tracker now uses local cache before fetching asset metadata
1 parent 3d0f0e1 commit 46d8b35

File tree

2 files changed

+91
-33
lines changed

2 files changed

+91
-33
lines changed

packages/wallet/src/services/AssetsTracker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const splitCachedAndUncachedAssets = (
7171

7272
if (!isInBalance(id, balance)) {
7373
cachedAssets.set(id, cachedAssetInfo);
74-
} else if (staleAt && new Date(staleAt) < now) {
74+
} else if (staleAt && new Date(staleAt) >= now) {
7575
cachedAssets.set(id, cachedAssetInfo);
7676
} else {
7777
uncachedAssetIds.push(id);
@@ -96,7 +96,7 @@ export const createAssetService =
9696
) =>
9797
(assetIds: Cardano.AssetId[]) =>
9898
combineLatest([assetCache$, totalBalance$]).pipe(
99-
take(1), // Take only the first combination of emissions
99+
take(1),
100100
switchMap(([cache, totalValue]) => {
101101
const { cachedAssets, uncachedAssetIds } = splitCachedAndUncachedAssets(cache, totalValue, assetIds);
102102

packages/wallet/test/services/AssetsTracker.test.ts

Lines changed: 89 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
createAssetsTracker
1111
} from '../../src/services';
1212

13-
import { Observable, from, lastValueFrom, map, of, take, tap } from 'rxjs';
13+
import { Observable, from, lastValueFrom, of, tap } from 'rxjs';
1414
import { RetryBackoffConfig } from 'backoff-rxjs';
1515

1616
const createTxWithValues = (values: Partial<Cardano.Value>[]): Cardano.HydratedTx =>
@@ -32,6 +32,8 @@ const cip68AssetId = {
3232
)
3333
};
3434

35+
const ONE_WEEK = 7 * 24 * 60 * 60 * 1000;
36+
3537
const assetInfo = {
3638
PXL: { assetId: AssetId.PXL, nftMetadata: { name: 'nft' }, tokenMetadata: null } as Asset.AssetInfo,
3739
TSLA: { assetId: AssetId.TSLA, nftMetadata: null, tokenMetadata: null } as Asset.AssetInfo,
@@ -406,8 +408,8 @@ describe('createAssetService', () => {
406408
it('returns cached assets if all assets are fresh', () => {
407409
createTestScheduler().run(({ cold, expectObservable, flush }) => {
408410
const cachedAssets = new Map([
409-
[AssetId.TSLA, { assetId: AssetId.TSLA, staleAt: new Date() } as never],
410-
[AssetId.PXL, { assetId: AssetId.PXL, staleAt: new Date() } as never]
411+
[AssetId.TSLA, { assetId: AssetId.TSLA, staleAt: new Date(Date.now() + ONE_WEEK) } as never],
412+
[AssetId.PXL, { assetId: AssetId.PXL, staleAt: new Date(Date.now() + ONE_WEEK) } as never]
411413
]);
412414

413415
(assetProvider.getAssets as jest.Mock).mockImplementation(() => of([]));
@@ -447,10 +449,12 @@ describe('createAssetService', () => {
447449

448450
it('fetches uncached assets from the provider', () => {
449451
createTestScheduler().run(({ cold, expectObservable, flush }) => {
450-
const cachedAssets = new Map([[AssetId.TSLA, { assetId: AssetId.TSLA, staleAt: new Date() } as never]]);
452+
const cachedAssets = new Map([
453+
[AssetId.TSLA, { assetId: AssetId.TSLA, staleAt: new Date(Date.now() + ONE_WEEK) } as never]
454+
]);
451455
const fetchedAssets = [
452-
{ assetId: AssetId.PXL, nftMetadata: null },
453-
{ assetId: AssetId.Unit, nftMetadata: null }
456+
{ assetId: AssetId.PXL, nftMetadata: null, tokenMetadata: null },
457+
{ assetId: AssetId.Unit, nftMetadata: null, tokenMetadata: null }
454458
];
455459

456460
(assetProvider.getAssets as jest.Mock).mockImplementation(() => of(fetchedAssets));
@@ -466,21 +470,13 @@ describe('createAssetService', () => {
466470
onFatalError
467471
);
468472

469-
const result$ = assetService([AssetId.TSLA, AssetId.PXL, AssetId.Unit]).pipe(
470-
take(1),
471-
map((assets) =>
472-
assets.map((asset) => ({
473-
...asset,
474-
staleAt: 'normalized'
475-
}))
476-
)
477-
);
473+
const result$ = assetService([AssetId.TSLA, AssetId.PXL, AssetId.Unit]);
478474

479475
expectObservable(result$).toBe('(a|)', {
480476
a: [
481-
{ assetId: AssetId.TSLA, staleAt: 'normalized' },
482-
{ assetId: AssetId.PXL, nftMetadata: null, staleAt: 'normalized' },
483-
{ assetId: AssetId.Unit, nftMetadata: null, staleAt: 'normalized' }
477+
{ assetId: AssetId.TSLA, staleAt: expect.any(Date) },
478+
{ assetId: AssetId.PXL, nftMetadata: null, staleAt: expect.any(Date), tokenMetadata: null },
479+
{ assetId: AssetId.Unit, nftMetadata: null, staleAt: expect.any(Date), tokenMetadata: null }
484480
]
485481
});
486482

@@ -491,8 +487,8 @@ describe('createAssetService', () => {
491487
it('handles an empty cache and fetches all assets', () => {
492488
createTestScheduler().run(({ cold, expectObservable, flush }) => {
493489
const fetchedAssets = [
494-
{ assetId: AssetId.TSLA, nftMetadata: null },
495-
{ assetId: AssetId.PXL, nftMetadata: null }
490+
{ assetId: AssetId.TSLA, nftMetadata: null, tokenMetadata: null },
491+
{ assetId: AssetId.PXL, nftMetadata: null, tokenMetadata: null }
496492
];
497493

498494
(assetProvider.getAssets as jest.Mock).mockImplementation(() => of(fetchedAssets));
@@ -508,25 +504,87 @@ describe('createAssetService', () => {
508504
onFatalError
509505
);
510506

511-
const result$ = assetService([AssetId.TSLA, AssetId.PXL]).pipe(
512-
take(1),
513-
// eslint-disable-next-line sonarjs/no-identical-functions
514-
map((assets) =>
515-
assets.map((asset) => ({
516-
...asset,
517-
staleAt: 'normalized'
518-
}))
519-
)
507+
const result$ = assetService([AssetId.TSLA, AssetId.PXL]);
508+
509+
expectObservable(result$).toBe('(a|)', {
510+
a: [
511+
{ assetId: AssetId.TSLA, nftMetadata: null, staleAt: expect.any(Date), tokenMetadata: null },
512+
{ assetId: AssetId.PXL, nftMetadata: null, staleAt: expect.any(Date), tokenMetadata: null }
513+
]
514+
});
515+
516+
flush();
517+
});
518+
});
519+
520+
it('fetches stale assets from the provider', () => {
521+
createTestScheduler().run(({ cold, expectObservable, flush }) => {
522+
const cachedAssets = new Map([
523+
// Stale
524+
[
525+
AssetId.TSLA,
526+
{
527+
assetId: AssetId.TSLA,
528+
nftMetadata: { name: 'tsla_cached_name' },
529+
staleAt: new Date(Date.now() - ONE_WEEK)
530+
} as never
531+
],
532+
// Fresh
533+
[
534+
AssetId.PXL,
535+
{
536+
assetId: AssetId.PXL,
537+
nftMetadata: { name: 'pxl_cached_name' },
538+
staleAt: new Date(Date.now() + ONE_WEEK)
539+
} as never
540+
]
541+
]);
542+
543+
const fetchedAssets = [
544+
{ assetId: AssetId.TSLA, nftMetadata: { name: 'tsla_updated_name' }, tokenMetadata: null }
545+
];
546+
547+
(assetProvider.getAssets as jest.Mock).mockImplementation((args: { assetIds: Cardano.AssetId[] }) => {
548+
expect(args.assetIds).toEqual([AssetId.TSLA]); // Only stale asset should be requested
549+
return of(fetchedAssets);
550+
});
551+
552+
const assetCache$ = cold('(a|)', { a: cachedAssets });
553+
const totalBalance$ = cold('(a|)', {
554+
a: {
555+
assets: new Map([
556+
[AssetId.TSLA, 1000n],
557+
[AssetId.PXL, 1000n]
558+
]),
559+
coins: 0n
560+
}
561+
});
562+
563+
const assetService = createAssetService(
564+
assetProvider,
565+
assetCache$,
566+
totalBalance$,
567+
retryBackoffConfig,
568+
onFatalError
520569
);
521570

571+
const result$ = assetService([AssetId.TSLA, AssetId.PXL]);
572+
522573
expectObservable(result$).toBe('(a|)', {
523574
a: [
524-
{ assetId: AssetId.TSLA, nftMetadata: null, staleAt: 'normalized' },
525-
{ assetId: AssetId.PXL, nftMetadata: null, staleAt: 'normalized' }
575+
{ assetId: AssetId.PXL, nftMetadata: { name: 'pxl_cached_name' }, staleAt: expect.any(Date) },
576+
{
577+
assetId: AssetId.TSLA,
578+
nftMetadata: { name: 'tsla_updated_name' },
579+
staleAt: expect.any(Date),
580+
tokenMetadata: null
581+
}
526582
]
527583
});
528584

529585
flush();
586+
587+
expect(assetProvider.getAssets).toHaveBeenCalledTimes(1);
530588
});
531589
});
532590
});

0 commit comments

Comments
 (0)