Skip to content

Commit

Permalink
feat: added a bunch of tests (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexlwn123 authored Sep 24, 2024
1 parent 4cbe62d commit 908129f
Show file tree
Hide file tree
Showing 11 changed files with 346 additions and 32 deletions.
5 changes: 5 additions & 0 deletions .changeset/late-glasses-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fedimint/core-web': patch
---

Added TestingService for exposing internal state during tests. Added a bunch of tests.
5 changes: 5 additions & 0 deletions examples/nextjs/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
6 changes: 6 additions & 0 deletions packages/core-web/src/FedimintWallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const TESTING_FEDERATION =
beforeAll(() => {
randomTestingId = Math.random().toString(36).substring(2, 15)
wallet = new FedimintWallet()
expect(wallet._testing).toBeDefined()
expect(wallet._testing!.getRequestCounter()).toBe(1)
expect(wallet).toBeDefined()

// Cleanup after all tests
Expand All @@ -30,9 +32,11 @@ test('initial open & join', async () => {
// On initial open, it should return false
// because no federations have been joined
await expect(wallet.open(randomTestingId)).resolves.toBe(false)
const beforeJoin = wallet._testing!.getRequestCounter()
await expect(
wallet.joinFederation(TESTING_FEDERATION, randomTestingId),
).resolves.toBeUndefined()
expect(wallet._testing!.getRequestCounter()).toBe(beforeJoin + 1)
expect(wallet.isOpen()).toBe(true)
await expect(wallet.waitForOpen()).resolves.toBeUndefined()
})
Expand All @@ -44,6 +48,7 @@ test('Error on open & join if wallet is already open', async () => {
// Test opening an already open wallet
try {
await wallet.open(randomTestingId)
expect.unreachable('Opening a wallet should fail on an already open wallet')
} catch (error) {
expect(error).toBeInstanceOf(Error)
expect((error as Error).message).toBe('The FedimintWallet is already open.')
Expand All @@ -52,6 +57,7 @@ test('Error on open & join if wallet is already open', async () => {
// Test joining federation on an already open wallet
try {
await wallet.joinFederation(TESTING_FEDERATION, randomTestingId)
expect.unreachable('Joining a federation should fail on an open wallet')
} catch (error) {
expect(error).toBeInstanceOf(Error)
expect((error as Error).message).toBe(
Expand Down
48 changes: 27 additions & 21 deletions packages/core-web/src/FedimintWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@ import {
RecoveryService,
} from './services'
import { logger, type LogLevel } from './utils/logger'
import { TestingService } from './services/TestingService'

const DEFAULT_CLIENT_NAME = 'fm-default' as const

export class FedimintWallet {
private client: WorkerClient
private _client: WorkerClient

public balance: BalanceService
public mint: MintService
public lightning: LightningService
public federation: FederationService
public recovery: RecoveryService

private openPromise: Promise<void> | null = null
private resolveOpen: () => void = () => {}
public _testing?: TestingService

private _openPromise: Promise<void> | null = null
private _resolveOpen: () => void = () => {}
private _isOpen: boolean = false

/**
Expand Down Expand Up @@ -54,15 +57,18 @@ export class FedimintWallet {
* lazyWallet.open();
*/
constructor(lazy: boolean = false) {
this.openPromise = new Promise((resolve) => {
this.resolveOpen = resolve
this._openPromise = new Promise((resolve) => {
this._resolveOpen = resolve
})
this.client = new WorkerClient()
this.mint = new MintService(this.client)
this.lightning = new LightningService(this.client)
this.balance = new BalanceService(this.client)
this.federation = new FederationService(this.client)
this.recovery = new RecoveryService(this.client)
this._client = new WorkerClient()
this.mint = new MintService(this._client)
this.lightning = new LightningService(this._client)
this.balance = new BalanceService(this._client)
this.federation = new FederationService(this._client)
this.recovery = new RecoveryService(this._client)
if (process.env.NODE_ENV === 'test') {
this._testing = new TestingService(this._client)
}

logger.info('FedimintWallet instantiated')

Expand All @@ -73,25 +79,25 @@ export class FedimintWallet {

async initialize() {
logger.info('Initializing WorkerClient')
await this.client.initialize()
await this._client.initialize()
logger.info('WorkerClient initialized')
}

async waitForOpen() {
if (this._isOpen) return Promise.resolve()
return this.openPromise
return this._openPromise
}

async open(clientName: string = DEFAULT_CLIENT_NAME) {
await this.client.initialize()
await this._client.initialize()
// TODO: Determine if this should be safe or throw
if (this._isOpen) throw new Error('The FedimintWallet is already open.')
const { success } = await this.client.sendSingleMessage('open', {
const { success } = await this._client.sendSingleMessage('open', {
clientName,
})
if (success) {
this._isOpen = !!success
this.resolveOpen()
this._resolveOpen()
}
return success
}
Expand All @@ -100,19 +106,19 @@ export class FedimintWallet {
inviteCode: string,
clientName: string = DEFAULT_CLIENT_NAME,
) {
await this.client.initialize()
await this._client.initialize()
// TODO: Determine if this should be safe or throw
if (this._isOpen)
throw new Error(
'The FedimintWallet is already open. You can only call `joinFederation` on closed clients.',
)
const response = await this.client.sendSingleMessage('join', {
const response = await this._client.sendSingleMessage('join', {
inviteCode,
clientName,
})
if (response.success) {
this._isOpen = true
this.resolveOpen()
this._resolveOpen()
}
}

Expand All @@ -121,9 +127,9 @@ export class FedimintWallet {
* After this call, the FedimintWallet instance should be discarded.
*/
async cleanup() {
this.openPromise = null
this._openPromise = null
this._isOpen = false
this.client.cleanup()
this._client.cleanup()
}

isOpen() {
Expand Down
55 changes: 55 additions & 0 deletions packages/core-web/src/services/BalanceService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { test, expect } from 'vitest'
import { FedimintWallet } from '../FedimintWallet'
import { beforeAll } from 'vitest'

let randomTestingId: string
let wallet: FedimintWallet
// Testnet
const TESTING_FEDERATION =
'fed11qgqrgvnhwden5te0v9k8q6rp9ekh2arfdeukuet595cr2ttpd3jhq6rzve6zuer9wchxvetyd938gcewvdhk6tcqqysptkuvknc7erjgf4em3zfh90kffqf9srujn6q53d6r056e4apze5cw27h75'

beforeAll(async () => {
randomTestingId = Math.random().toString(36).substring(2, 15)
wallet = new FedimintWallet()
expect(wallet).toBeDefined()
await expect(
wallet.joinFederation(TESTING_FEDERATION, randomTestingId),
).resolves.toBeUndefined()
expect(wallet.isOpen()).toBe(true)

// Cleanup after all tests
return async () => {
// clear up browser resources
await wallet.cleanup()
// remove the wallet db
indexedDB.deleteDatabase(randomTestingId)
// swap out the randomTestingId for a new one, to avoid raciness
randomTestingId = Math.random().toString(36).substring(2, 15)
}
})

test('getBalance should be initially zero', async () => {
expect(wallet).toBeDefined()
expect(wallet.isOpen()).toBe(true)
const beforeGetBalance = wallet._testing!.getRequestCounter()
await expect(wallet.balance.getBalance()).resolves.toEqual(0)
expect(wallet._testing!.getRequestCounter()).toBe(beforeGetBalance + 1)
})

test('subscribe balance', async () => {
expect(wallet).toBeDefined()
expect(wallet.isOpen()).toBe(true)

const counterBefore = wallet._testing!.getRequestCounter()
const callbacksBefore = wallet._testing!.getRequestCallbackMap().size
const unsubscribe = await wallet.balance.subscribeBalance((balance) => {
expect(balance).toEqual(0)
})
expect(wallet._testing!.getRequestCounter()).toBe(counterBefore + 1)
expect(wallet._testing!.getRequestCallbackMap().size).toBe(
callbacksBefore + 1,
)

await expect(wallet.balance.getBalance()).resolves.toEqual(0)
expect(unsubscribe()).toBeUndefined()
})
74 changes: 74 additions & 0 deletions packages/core-web/src/services/FederationService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { test, expect } from 'vitest'
import { FedimintWallet } from '../FedimintWallet'
import { beforeAll } from 'vitest'

let randomTestingId: string
let wallet: FedimintWallet
// Testnet
const TESTING_FEDERATION =
'fed11qgqrgvnhwden5te0v9k8q6rp9ekh2arfdeukuet595cr2ttpd3jhq6rzve6zuer9wchxvetyd938gcewvdhk6tcqqysptkuvknc7erjgf4em3zfh90kffqf9srujn6q53d6r056e4apze5cw27h75'

beforeAll(async () => {
randomTestingId = Math.random().toString(36).substring(2, 15)
wallet = new FedimintWallet()
expect(wallet).toBeDefined()
await expect(
wallet.joinFederation(TESTING_FEDERATION, randomTestingId),
).resolves.toBeUndefined()
expect(wallet.isOpen()).toBe(true)

// Cleanup after all tests
return async () => {
// clear up browser resources
await wallet.cleanup()
// remove the wallet db
indexedDB.deleteDatabase(randomTestingId)
// swap out the randomTestingId for a new one, to avoid raciness
randomTestingId = Math.random().toString(36).substring(2, 15)
}
})

test('getConfig should return the federation config', async () => {
expect(wallet).toBeDefined()
expect(wallet.isOpen()).toBe(true)
const counterBefore = wallet._testing!.getRequestCounter()
await expect(wallet.federation.getConfig()).resolves.toMatchObject({
api_endpoints: expect.any(Object),
broadcast_public_keys: expect.any(Object),
consensus_version: expect.any(Object),
meta: expect.any(Object),
modules: expect.any(Object),
})
expect(wallet._testing!.getRequestCounter()).toBe(counterBefore + 1)
})

test('getFederationId should return the federation id', async () => {
expect(wallet).toBeDefined()
expect(wallet.isOpen()).toBe(true)

const counterBefore = wallet._testing!.getRequestCounter()
const federationId = await wallet.federation.getFederationId()
expect(federationId).toBeTypeOf('string')
expect(federationId).toHaveLength(64)
expect(wallet._testing!.getRequestCounter()).toBe(counterBefore + 1)
})

test('getInviteCode should return the invite code', async () => {
expect(wallet).toBeDefined()
expect(wallet.isOpen()).toBe(true)

const counterBefore = wallet._testing!.getRequestCounter()
const inviteCode = await wallet.federation.getInviteCode(0)
expect(inviteCode).toBeTypeOf('string')
expect(inviteCode).toHaveLength(154)
expect(wallet._testing!.getRequestCounter()).toBe(counterBefore + 1)
})

test('listOperations should return the list of operations', async () => {
expect(wallet).toBeDefined()
expect(wallet.isOpen()).toBe(true)

const counterBefore = wallet._testing!.getRequestCounter()
await expect(wallet.federation.listOperations()).resolves.toMatchObject([])
expect(wallet._testing!.getRequestCounter()).toBe(counterBefore + 1)
})
10 changes: 0 additions & 10 deletions packages/core-web/src/services/FederationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,6 @@ export class FederationService {
return await this.client.rpcSingle('', 'get_invite_code', { peer })
}

async joinFederation(inviteCode: string, clientName: string): Promise<void> {
const response = await this.client.sendSingleMessage('join', {
inviteCode,
clientName,
})
if (!response.success) {
throw new Error('Failed to join federation')
}
}

async listOperations(): Promise<JSONValue[]> {
return await this.client.rpcSingle('', 'list_operations', {})
}
Expand Down
Loading

0 comments on commit 908129f

Please sign in to comment.