- Added new dependencies: `adler2`, `crc32fast`, `flate2`, `miniz_oxide`, and `libredox`. - Updated existing dependencies: `tokio-rustls` to version 0.26.4 and `filetime` to version 0.2.27. - Removed the `backup.rs` file as it is no longer needed. - Introduced tests for configuration and credential management. - Enhanced the `identity` module to generate W3C compliant DID documents. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
212 lines
6.1 KiB
TypeScript
212 lines
6.1 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
|
|
const mockFetch = vi.fn()
|
|
vi.stubGlobal('fetch', mockFetch)
|
|
|
|
// Import after stubbing fetch
|
|
const { rpcClient } = await import('../rpc-client')
|
|
|
|
function jsonResponse(body: unknown, status = 200, statusText = 'OK'): Response {
|
|
return {
|
|
ok: status >= 200 && status < 300,
|
|
status,
|
|
statusText,
|
|
json: () => Promise.resolve(body),
|
|
headers: new Headers(),
|
|
redirected: false,
|
|
type: 'basic' as ResponseType,
|
|
url: '',
|
|
clone: () => jsonResponse(body, status, statusText),
|
|
body: null,
|
|
bodyUsed: false,
|
|
arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
|
|
blob: () => Promise.resolve(new Blob()),
|
|
formData: () => Promise.resolve(new FormData()),
|
|
text: () => Promise.resolve(JSON.stringify(body)),
|
|
bytes: () => Promise.resolve(new Uint8Array()),
|
|
}
|
|
}
|
|
|
|
describe('marketplaceDiscover', () => {
|
|
beforeEach(() => {
|
|
mockFetch.mockReset()
|
|
vi.useFakeTimers({ shouldAdvanceTime: true })
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it('returns apps array and relay_count on success', async () => {
|
|
const payload = {
|
|
apps: [
|
|
{
|
|
manifest: {
|
|
app_id: 'bitcoin',
|
|
name: 'Bitcoin Core',
|
|
version: '27.0',
|
|
description: { short: 'Full node', long: 'Bitcoin Core full node' },
|
|
author: { name: 'Bitcoin', did: 'did:key:z111', nostr_pubkey: 'npub1abc' },
|
|
container: { image: 'bitcoin:27.0', ports: [{ container: 8333, host: 8333 }] },
|
|
category: 'bitcoin',
|
|
icon_url: '/icons/bitcoin.png',
|
|
repo_url: 'https://github.com/bitcoin/bitcoin',
|
|
license: 'MIT',
|
|
},
|
|
trust_score: 95,
|
|
trust_tier: 'verified',
|
|
relay_count: 8,
|
|
first_seen: '2025-01-15T00:00:00Z',
|
|
nostr_pubkey: 'npub1abc',
|
|
},
|
|
],
|
|
relay_count: 12,
|
|
}
|
|
|
|
mockFetch.mockResolvedValueOnce(jsonResponse({ result: payload }))
|
|
|
|
const result = await rpcClient.marketplaceDiscover()
|
|
|
|
expect(result.apps).toHaveLength(1)
|
|
expect(result.apps[0]!.manifest.app_id).toBe('bitcoin')
|
|
expect(result.apps[0]!.manifest.name).toBe('Bitcoin Core')
|
|
expect(result.apps[0]!.trust_score).toBe(95)
|
|
expect(result.relay_count).toBe(12)
|
|
|
|
const body = JSON.parse(mockFetch.mock.calls[0]![1].body)
|
|
expect(body.method).toBe('marketplace.discover')
|
|
expect(body.params).toEqual({})
|
|
})
|
|
|
|
it('handles empty results', async () => {
|
|
const payload = {
|
|
apps: [],
|
|
relay_count: 0,
|
|
}
|
|
|
|
mockFetch.mockResolvedValueOnce(jsonResponse({ result: payload }))
|
|
|
|
const result = await rpcClient.marketplaceDiscover()
|
|
|
|
expect(result.apps).toEqual([])
|
|
expect(result.apps).toHaveLength(0)
|
|
expect(result.relay_count).toBe(0)
|
|
})
|
|
})
|
|
|
|
describe('diskStatus', () => {
|
|
beforeEach(() => {
|
|
mockFetch.mockReset()
|
|
vi.useFakeTimers({ shouldAdvanceTime: true })
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it('returns expected fields', async () => {
|
|
const payload = {
|
|
used_bytes: 500_000_000_000,
|
|
total_bytes: 1_000_000_000_000,
|
|
free_bytes: 500_000_000_000,
|
|
used_percent: 50,
|
|
level: 'ok' as const,
|
|
}
|
|
|
|
mockFetch.mockResolvedValueOnce(jsonResponse({ result: payload }))
|
|
|
|
const result = await rpcClient.diskStatus()
|
|
|
|
expect(result.used_bytes).toBe(500_000_000_000)
|
|
expect(result.total_bytes).toBe(1_000_000_000_000)
|
|
expect(result.free_bytes).toBe(500_000_000_000)
|
|
expect(result.used_percent).toBe(50)
|
|
expect(result.level).toBe('ok')
|
|
|
|
const body = JSON.parse(mockFetch.mock.calls[0]![1].body)
|
|
expect(body.method).toBe('system.disk-status')
|
|
})
|
|
|
|
it('level is warning when percent >= 85', async () => {
|
|
const payload = {
|
|
used_bytes: 850_000_000_000,
|
|
total_bytes: 1_000_000_000_000,
|
|
free_bytes: 150_000_000_000,
|
|
used_percent: 85,
|
|
level: 'warning' as const,
|
|
}
|
|
|
|
mockFetch.mockResolvedValueOnce(jsonResponse({ result: payload }))
|
|
|
|
const result = await rpcClient.diskStatus()
|
|
|
|
expect(result.level).toBe('warning')
|
|
expect(result.used_percent).toBe(85)
|
|
})
|
|
|
|
it('level is critical when percent >= 90', async () => {
|
|
const payload = {
|
|
used_bytes: 950_000_000_000,
|
|
total_bytes: 1_000_000_000_000,
|
|
free_bytes: 50_000_000_000,
|
|
used_percent: 95,
|
|
level: 'critical' as const,
|
|
}
|
|
|
|
mockFetch.mockResolvedValueOnce(jsonResponse({ result: payload }))
|
|
|
|
const result = await rpcClient.diskStatus()
|
|
|
|
expect(result.level).toBe('critical')
|
|
expect(result.used_percent).toBe(95)
|
|
})
|
|
})
|
|
|
|
describe('diskCleanup', () => {
|
|
beforeEach(() => {
|
|
mockFetch.mockReset()
|
|
vi.useFakeTimers({ shouldAdvanceTime: true })
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it('returns freed_bytes and actions array', async () => {
|
|
const payload = {
|
|
freed_bytes: 2_000_000_000,
|
|
freed_human: '2 GB',
|
|
actions: ['Removed 5 dangling images', 'Cleared build cache'],
|
|
}
|
|
|
|
mockFetch.mockResolvedValueOnce(jsonResponse({ result: payload }))
|
|
|
|
const result = await rpcClient.diskCleanup()
|
|
|
|
expect(result.freed_bytes).toBe(2_000_000_000)
|
|
expect(result.freed_human).toBe('2 GB')
|
|
expect(result.actions).toHaveLength(2)
|
|
expect(result.actions[0]).toBe('Removed 5 dangling images')
|
|
expect(result.actions[1]).toBe('Cleared build cache')
|
|
|
|
const body = JSON.parse(mockFetch.mock.calls[0]![1].body)
|
|
expect(body.method).toBe('system.disk-cleanup')
|
|
})
|
|
|
|
it('uses 60s timeout', async () => {
|
|
const abortError = Object.assign(new Error('The operation was aborted.'), { name: 'AbortError' })
|
|
mockFetch.mockRejectedValue(abortError)
|
|
|
|
const promise = rpcClient.diskCleanup()
|
|
|
|
// The call should eventually reject with timeout after retries
|
|
await expect(promise).rejects.toThrow('Request timeout')
|
|
|
|
// Verify all 3 attempts used the signal (timeout is set via AbortController)
|
|
expect(mockFetch).toHaveBeenCalledTimes(3)
|
|
for (const call of mockFetch.mock.calls) {
|
|
expect(call[1].signal).toBeInstanceOf(AbortSignal)
|
|
}
|
|
})
|
|
})
|