Activities/Wallet Refactor

This commit is contained in:
Jim Hamill
2025-04-18 05:17:20 +00:00
parent 408d3b8d8c
commit 7e4508cf5a
18 changed files with 706 additions and 1111 deletions
+60 -110
View File
@@ -1,27 +1,24 @@
import { describe, test, expect, beforeEach } from 'vitest';
import { Activities } from '~/src/wallet';
import { StoreInMemory } from '~/src/storage';
import { Activities, StoreInMemory } from '../../src/storage';
describe('Activities', () => {
let store: StoreInMemory;
let activities: Activities;
beforeEach(() => {
store = new StoreInMemory();
activities = new Activities(store);
beforeEach(async () => {
activities = await Activities.fromStores(StoreInMemory.from());
});
test('should add activity with auto-generated timestamp', async () => {
const activity = {
type: 'WalletName',
id: 'wallet1',
name: 'WalletName',
entityId: 'wallet1',
value: 'Test Wallet',
};
await activities.addActivity(activity);
await activities.compileSnapshot();
await activities.add(activity);
const storedActivity = activities.getActivity('WalletName', 'wallet1');
const storedActivity = activities.get('WalletName', 'wallet1');
expect(storedActivity).toBeDefined();
expect(storedActivity?.timestamp).toBeDefined();
expect(storedActivity?.value).toBe('Test Wallet');
@@ -31,15 +28,14 @@ describe('Activities', () => {
const timestamp = BigInt(1234567890000);
const activity = {
timestamp,
type: 'WalletName',
id: 'wallet1',
name: 'WalletName',
entityId: 'wallet1',
value: 'Test Wallet',
};
await activities.addActivity(activity);
const snapshot = await activities.compileSnapshot();
await activities.add(activity);
const storedActivity = activities.getActivity('WalletName', 'wallet1');
const storedActivity = activities.get('WalletName', 'wallet1');
expect(storedActivity?.timestamp).toBe(timestamp);
});
@@ -47,22 +43,21 @@ describe('Activities', () => {
const oldTimestamp = BigInt(1000);
const newTimestamp = BigInt(2000);
await activities.addActivity({
await activities.add({
timestamp: oldTimestamp,
type: 'WalletName',
id: 'wallet1',
name: 'WalletName',
entityId: 'wallet1',
value: 'Old Name',
});
await activities.addActivity({
await activities.add({
timestamp: newTimestamp,
type: 'WalletName',
id: 'wallet1',
name: 'WalletName',
entityId: 'wallet1',
value: 'New Name',
});
await activities.compileSnapshot();
const activity = activities.getActivity('WalletName', 'wallet1');
const activity = activities.get('WalletName', 'wallet1');
expect(activity?.value).toBe('New Name');
expect(activity?.timestamp).toBe(newTimestamp);
@@ -76,22 +71,21 @@ describe('Activities', () => {
const oldTimestamp = BigInt(1000);
const newTimestamp = BigInt(2000);
await activities.addActivity({
await activities.add({
timestamp: oldTimestamp,
type: 'TransactionFirstSeen',
id: 'tx1',
name: 'TransactionFirstSeen',
entityId: 'tx1',
value: 'First Value',
});
await activities.addActivity({
await activities.add({
timestamp: newTimestamp,
type: 'TransactionFirstSeen',
id: 'tx1',
name: 'TransactionFirstSeen',
entityId: 'tx1',
value: 'Second Value',
});
const snapshot = await activities.compileSnapshot();
const activity = activities.getActivity('TransactionFirstSeen', 'tx1');
const activity = activities.get('TransactionFirstSeen', 'tx1');
expect(activity?.value).toBe('First Value');
expect(activity?.timestamp).toBe(oldTimestamp);
@@ -101,25 +95,24 @@ describe('Activities', () => {
// Policy that keeps activity with highest value
activities.registerPolicy('CustomType', (existing, newer) => {
if (!existing) return newer;
return existing.value > newer.value ? existing : newer;
return existing.timestamp < newer.timestamp ? existing : newer;
});
await activities.addActivity({
await activities.add({
timestamp: BigInt(1000),
type: 'CustomType',
id: 'custom1',
name: 'CustomType',
entityId: 'custom1',
value: 50,
});
await activities.addActivity({
await activities.add({
timestamp: BigInt(2000),
type: 'CustomType',
id: 'custom1',
name: 'CustomType',
entityId: 'custom1',
value: 30,
});
const snapshot = await activities.compileSnapshot();
const activity = activities.getActivity('CustomType', 'custom1');
const activity = activities.get('CustomType', 'custom1');
expect(activity?.value).toBe(50);
});
@@ -128,55 +121,50 @@ describe('Activities', () => {
const testActivities = [
{
timestamp: BigInt(3000),
type: 'WalletName',
id: 'wallet1',
name: 'WalletName',
entityId: 'wallet1',
value: 'Name 3',
},
{
timestamp: BigInt(1000),
type: 'WalletName',
id: 'wallet1',
name: 'WalletName',
entityId: 'wallet1',
value: 'Name 1',
},
{
timestamp: BigInt(2000),
type: 'WalletName',
id: 'wallet1',
name: 'WalletName',
entityId: 'wallet1',
value: 'Name 2',
},
];
for (const activity of testActivities) {
await activities.addActivity(activity);
await activities.add(activity);
}
await activities.compileSnapshot();
const finalActivity = activities.getActivity('WalletName', 'wallet1');
const finalActivity = activities.get('WalletName', 'wallet1');
expect(finalActivity?.value).toBe('Name 3');
});
test('should handle multiple activity types independently', async () => {
await activities.addActivity({
await activities.add({
timestamp: BigInt(1000),
type: 'WalletName',
id: 'wallet1',
name: 'WalletName',
entityId: 'wallet1',
value: 'My Wallet',
});
await activities.addActivity({
await activities.add({
timestamp: BigInt(2000),
type: 'WalletDescription',
id: 'wallet1',
name: 'WalletDescription',
entityId: 'wallet1',
value: 'My Description',
});
await activities.compileSnapshot();
expect(activities.getActivity('WalletName', 'wallet1')?.value).toBe(
'My Wallet',
);
expect(activities.getActivity('WalletDescription', 'wallet1')?.value).toBe(
expect(activities.get('WalletName', 'wallet1')?.value).toBe('My Wallet');
expect(activities.get('WalletDescription', 'wallet1')?.value).toBe(
'My Description',
);
});
@@ -184,75 +172,37 @@ describe('Activities', () => {
test('should update snapshot immediately when activity is added', async () => {
const activity = {
timestamp: BigInt(1000),
type: 'WalletName',
id: 'wallet1',
name: 'WalletName',
entityId: 'wallet1',
value: 'Test Wallet',
};
await activities.addActivity(activity);
await activities.add(activity);
// Check snapshot without calling compileSnapshot
const storedActivity = activities.getActivity('WalletName', 'wallet1');
const storedActivity = activities.get('WalletName', 'wallet1');
expect(storedActivity?.value).toBe('Test Wallet');
});
test('should handle missing timestamp gracefully', async () => {
const activity = {
type: 'WalletName',
id: 'wallet1',
name: 'WalletName',
entityId: 'wallet1',
value: 'Test Wallet',
};
await activities.addActivity(activity);
await activities.compileSnapshot();
const storedActivity = activities.getActivity('WalletName', 'wallet1');
await activities.add(activity);
const storedActivity = activities.get('WalletName', 'wallet1');
expect(storedActivity).toBeDefined();
expect(storedActivity?.timestamp).toBeDefined();
expect(typeof storedActivity?.timestamp).toBe('bigint');
});
test('should handle store errors gracefully', async () => {
// Simulate store failure
store.set = async () => {
throw new Error('Store error');
};
await expect(
activities.addActivity({
type: 'WalletName',
id: 'wallet1',
value: 'Test',
}),
).rejects.toThrow('Store error');
});
test('should handle concurrent activity additions', async () => {
const promises: Promise<void>[] = []; // Add explicit type annotation
for (let i = 0; i < 10; i++) {
promises.push(
activities.addActivity({
timestamp: BigInt(i),
type: 'WalletName',
id: 'wallet1',
value: `Value ${i}`,
}),
);
}
await Promise.all(promises);
await activities.compileSnapshot();
// Should have the last value
expect(activities.getActivity('WalletName', 'wallet1')?.value).toBe(
'Value 9',
);
});
/*
test('should cleanup old activities from store when newer versions are added', async () => {
// Add first activity
await activities.addActivity({
await activities.add({
timestamp: BigInt(1000),
type: 'WalletName',
id: 'wallet1',
@@ -262,7 +212,7 @@ describe('Activities', () => {
expect(Object.keys(await store.all()).length).toBe(1);
// Add newer version - should trigger cleanup of old version
await activities.addActivity({
await activities.add({
timestamp: BigInt(2000),
type: 'WalletName',
id: 'wallet1',
@@ -273,7 +223,7 @@ describe('Activities', () => {
expect(Object.keys(await store.all()).length).toBe(1);
// Add different type
await activities.addActivity({
await activities.add({
timestamp: BigInt(1500),
type: 'WalletDescription',
id: 'wallet1',