diff --git a/packages/upload-api/test/helpers/context.js b/packages/upload-api/test/helpers/context.js index 5246e9a24..edbf12750 100644 --- a/packages/upload-api/test/helpers/context.js +++ b/packages/upload-api/test/helpers/context.js @@ -92,8 +92,8 @@ export const createContext = async ( dealTrackerService: { connection: dealTrackerConnection, invocationConfig: { - issuer: id, - with: id.did(), + issuer: signer, + with: signer.did(), audience: dealTrackerSigner, }, }, diff --git a/packages/w3up-client/package.json b/packages/w3up-client/package.json index 1bf0aec28..310f072d2 100644 --- a/packages/w3up-client/package.json +++ b/packages/w3up-client/package.json @@ -98,13 +98,14 @@ "build": "tsc --build", "check": "tsc --build", "prepare": "npm run build", - "test": "npm-run-all -p -r mock test:all", + "test": "npm-run-all -p -r mock:* test:all", "test:all": "run-s test:node test:browser", "test:node": "hundreds -r html -r text mocha 'test/**/!(*.browser).test.js' -n experimental-vm-modules -n no-warnings -n stack-trace-limit=1000", "test:browser": "playwright-test --runner mocha 'test/**/!(*.node).test.js'", "mock": "run-p mock:*", - "mock:bucket-200": "PORT=9200 STATUS=200 node test/helpers/bucket-server.js", + "mock:bucket-200": "PORT=8989 STATUS=200 node test/helpers/bucket-server.js", "mock:receipts-server": "PORT=9201 node test/helpers/receipts-server.js", + "coverage": "c8 report -r html && open coverage/index.html", "rc": "npm version prerelease --preid rc", "docs": "npm run build && typedoc --out docs-generated" }, diff --git a/packages/w3up-client/src/ability.js b/packages/w3up-client/src/ability.js index 19f227098..062bd92a4 100644 --- a/packages/w3up-client/src/ability.js +++ b/packages/w3up-client/src/ability.js @@ -14,7 +14,6 @@ const setOfAbilities = new Set(abilitiesAsStrings) * @param {string[]} abilities * @returns {import('@web3-storage/capabilities/types').ServiceAbility[]} */ -/* c8 ignore next */ export function asAbilities(abilities) { for (const ability of abilities) { if ( diff --git a/packages/w3up-client/src/capability/subscription.js b/packages/w3up-client/src/capability/subscription.js index 8f6a13a63..373b2e2f7 100644 --- a/packages/w3up-client/src/capability/subscription.js +++ b/packages/w3up-client/src/capability/subscription.js @@ -11,8 +11,10 @@ export class SubscriptionClient extends Base { * * @param {import('@web3-storage/access').AccountDID} account */ + /* c8 ignore next */ async list(account) { const out = await list({ agent: this.agent }, { account }) + /* c8 ignore next 8 */ if (!out.ok) { throw new Error( `failed ${SubscriptionCapabilities.list.can} invocation`, diff --git a/packages/w3up-client/src/capability/usage.js b/packages/w3up-client/src/capability/usage.js index aeccc715c..ebefc46b3 100644 --- a/packages/w3up-client/src/capability/usage.js +++ b/packages/w3up-client/src/capability/usage.js @@ -14,6 +14,7 @@ export class UsageClient extends Base { */ async report(space, period) { const out = await report({ agent: this.agent }, { space, period }) + /* c8 ignore next 7 */ if (!out.ok) { throw new Error(`failed ${UsageCapabilities.report.can} invocation`, { cause: out.error, diff --git a/packages/w3up-client/test/ability.test.js b/packages/w3up-client/test/ability.test.js index 87d807f2c..f75a8f148 100644 --- a/packages/w3up-client/test/ability.test.js +++ b/packages/w3up-client/test/ability.test.js @@ -1,13 +1,20 @@ -import assert from 'assert' -import { asAbilities } from '../src/ability.js' +import { asAbilities } from '@web3-storage/w3up-client' +import * as Test from './test.js' -describe('abilities', () => { - it('should return the passed argument if all abilities are valid', async () => { +/** + * @type {Test.Suite} + */ +export const testAbilities = { + 'should return the passed argument if all abilities are valid': async ( + assert + ) => { const abilities = ['store/add', 'upload/add'] assert.equal(asAbilities(abilities), abilities) - }) + }, - it('should throw an error if one of the abilities is not supported', async () => { + 'should throw an error if one of the abilities is not supported': async ( + assert + ) => { assert.throws( () => { asAbilities(['foo/bar']) @@ -17,5 +24,7 @@ describe('abilities', () => { message: 'foo/bar is not a supported capability', } ) - }) -}) + }, +} + +Test.test({ Abilities: testAbilities }) diff --git a/packages/w3up-client/test/access.test.js b/packages/w3up-client/test/access.test.js index 9cdf83f91..9dc73cb03 100644 --- a/packages/w3up-client/test/access.test.js +++ b/packages/w3up-client/test/access.test.js @@ -2,10 +2,7 @@ import * as Test from './test.js' import * as Access from '../src/capability/access.js' import * as Result from '../src/result.js' -/** - * @type {Test.Suite} - */ -export const testAccess = { +export const testAccess = Test.withContext({ 'capability.access.request': async ( assert, { client, mail, grantAccess } @@ -32,6 +29,6 @@ export const testAccess = { await access.save() assert.ok(client.proofs().length > 0) }, -} +}) Test.test({ Access: testAccess }) diff --git a/packages/w3up-client/test/account.test.js b/packages/w3up-client/test/account.test.js index 26a1fbf5c..96a9043b7 100644 --- a/packages/w3up-client/test/account.test.js +++ b/packages/w3up-client/test/account.test.js @@ -6,7 +6,7 @@ import * as Result from '../src/result.js' /** * @type {Test.Suite} */ -export const testAccount = { +export const testAccount = Test.withContext({ 'list accounts': async (assert, { client, mail, grantAccess }) => { const email = 'alice@web.mail' @@ -286,6 +286,6 @@ export const testAccount = { assert.deepEqual(client.currentSpace()?.did(), space.did()) }, -} +}) Test.test({ Account: testAccount }) diff --git a/packages/w3up-client/test/capability/access.test.js b/packages/w3up-client/test/capability/access.test.js index 29044500b..61702a9ff 100644 --- a/packages/w3up-client/test/capability/access.test.js +++ b/packages/w3up-client/test/capability/access.test.js @@ -1,49 +1,71 @@ -import assert from 'assert' -import { create as createServer, provide } from '@ucanto/server' -import * as CAR from '@ucanto/transport/car' -import * as Signer from '@ucanto/principal/ed25519' -import * as AccessCapabilities from '@web3-storage/capabilities/access' import { AgentData } from '@web3-storage/access/agent' -import { mockService, mockServiceConf } from '../helpers/mocks.js' import { Client } from '../../src/client.js' -import { validateAuthorization } from '../helpers/utils.js' - -describe('AccessClient', () => { - describe('claim', () => { - it('should claim delegations', async () => { - const service = mockService({ - access: { - claim: provide(AccessCapabilities.claim, ({ invocation }) => { - assert.equal(invocation.issuer.did(), alice.agent.did()) - assert.equal(invocation.capabilities.length, 1) - const invCap = invocation.capabilities[0] - assert.equal(invCap.can, AccessCapabilities.claim.can) - return { - ok: { - delegations: {}, - }, - } - }), +import * as Upload from '@web3-storage/capabilities/upload' +import * as Test from '../test.js' + +export const AccessClient = Test.withContext({ + claim: { + 'should claim delegations': async (assert, { connection }) => { + const alice = new Client(await AgentData.create(), { + // @ts-ignore + serviceConf: { + access: connection, + upload: connection, }, }) - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) + const delegations = await alice.capability.access.claim() + assert.deepEqual(delegations, []) + }, + 'should delegate and then claim': async ( + assert, + { connection, provisionsStorage } + ) => { const alice = new Client(await AgentData.create(), { // @ts-ignore - serviceConf: await mockServiceConf(server), + serviceConf: { + access: connection, + upload: connection, + }, }) + const space = await alice.createSpace('upload-test') + const auth = await space.createAuthorization(alice) + await alice.addSpace(auth) + await alice.setCurrentSpace(space.did()) - const delegations = await alice.capability.access.claim() + // Then we setup a billing for this account + await provisionsStorage.put({ + // @ts-expect-error + provider: connection.id.did(), + account: alice.agent.did(), + consumer: space.did(), + }) - assert(service.access.claim.called) - assert.equal(service.access.claim.callCount, 1) - assert.deepEqual(delegations, []) - }) - }) + const bob = new Client(await AgentData.create(), { + // @ts-ignore + serviceConf: { + access: connection, + upload: connection, + }, + }) + + const uploadList = await Upload.list.delegate({ + issuer: alice.agent.issuer, + audience: bob, + with: space.did(), + }) + + const result = await alice.capability.access.delegate({ + delegations: [uploadList], + }) + + assert.ok(result.ok) + + const delegations = await bob.capability.access.claim() + assert.deepEqual(delegations, [uploadList]) + }, + }, }) + +Test.test({ AccessClient }) diff --git a/packages/w3up-client/test/capability/filecoin.test.js b/packages/w3up-client/test/capability/filecoin.test.js index fa388c67a..f2640bece 100644 --- a/packages/w3up-client/test/capability/filecoin.test.js +++ b/packages/w3up-client/test/capability/filecoin.test.js @@ -1,73 +1,10 @@ -import assert from 'assert' -import { - create as createServer, - provide, - provideAdvanced, - ok, -} from '@ucanto/server' -import * as CAR from '@ucanto/transport/car' -import * as Signer from '@ucanto/principal/ed25519' import { Filecoin as FilecoinCapabilities } from '@web3-storage/capabilities' -import { AgentData } from '@web3-storage/access/agent' - -import { randomAggregate, randomCargo } from '../helpers/random.js' -import { mockService, mockServiceConf } from '../helpers/mocks.js' -import { Client } from '../../src/client.js' -import { validateAuthorization } from '../helpers/utils.js' - -describe('FilecoinClient', () => { - describe('offer', () => { - it('should send an offer', async () => { - const service = mockService({ - filecoin: { - offer: provideAdvanced({ - capability: FilecoinCapabilities.offer, - handler: async ({ invocation, context }) => { - const invCap = invocation.capabilities[0] - assert.ok(invCap.nb) - - // Create effect for receipt with self signed queued operation - const submitfx = await FilecoinCapabilities.submit - .invoke({ - issuer: context.id, - audience: context.id, - with: context.id.did(), - nb: invCap.nb, - expiration: Infinity, - }) - .delegate() - - const acceptfx = await FilecoinCapabilities.accept - .invoke({ - issuer: context.id, - audience: context.id, - with: context.id.did(), - nb: invCap.nb, - expiration: Infinity, - }) - .delegate() - - return ok({ - piece: invCap.nb.piece, - }) - .fork(submitfx.link()) - .join(acceptfx.link()) - }, - }), - }, - }) - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) - - const alice = new Client(await AgentData.create(), { - // @ts-ignore - serviceConf: await mockServiceConf(server), - }) +import { randomAggregate, randomCAR, randomCargo } from '../helpers/random.js' +import * as Test from '../test.js' +export const FilecoinClient = Test.withContext({ + offer: { + 'should send an offer': async (assert, { client: alice }) => { const space = await alice.createSpace('test') const auth = await space.createAuthorization(alice) await alice.addSpace(auth) @@ -79,89 +16,84 @@ describe('FilecoinClient', () => { cargo.link ) - assert(service.filecoin.offer.called) - assert.equal(service.filecoin.offer.callCount, 1) - assert(res.out.ok) - assert(res.out.ok.piece.equals(cargo.link)) - assert(res.fx.join) - assert(res.fx.fork.length) - }) - }) - describe('info', () => { - it('should get piece info', async () => { + assert.ok(res.out.ok) + assert.equal(res.out.ok?.piece.toString(), cargo.link.toString()) + assert.ok(res.fx.join) + assert.ok(res.fx.fork.length > 0) + }, + }, + info: { + 'should get piece info': async ( + assert, + { client: alice, pieceStore, service, receiptStore } + ) => { const { pieces, aggregate } = await randomAggregate(10, 100) + const content = await randomCAR(100) const cargo = pieces[0] // compute proof for piece in aggregate const proof = aggregate.resolveProof(cargo.link) if (proof.error) { throw new Error('could not compute proof') } - /** @type {import('@web3-storage/capabilities/types').FilecoinInfoSuccess} */ - const filecoinAcceptResponse = { + + const space = await alice.createSpace('test') + const auth = await space.createAuthorization(alice) + await alice.addSpace(auth) + await alice.setCurrentSpace(space.did()) + + await pieceStore.put({ piece: cargo.link, - aggregates: [ - { + content: content.cid, + group: 'some-group', + status: 'accepted', + insertedAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }) + + const pieceAcceptInvocation = await FilecoinCapabilities.accept + .invoke({ + issuer: service.signer, + audience: service.signer, + with: service.signer.did(), + nb: { + piece: cargo.link, + content: content.cid, + }, + expiration: Infinity, + }) + .delegate() + + // @ts-ignore + await receiptStore.put({ + ran: pieceAcceptInvocation.link(), + out: { + ok: { + piece: cargo.link, aggregate: aggregate.link, inclusion: { subtree: proof.ok[0], index: proof.ok[1], }, }, - ], - deals: [ - { - aggregate: aggregate.link, - provider: 'f1111', - aux: { - dataType: 0n, - dataSource: { - dealID: 1138n, - }, - }, - }, - ], - } - const service = mockService({ - filecoin: { - info: provide(FilecoinCapabilities.info, ({ invocation }) => { - const invCap = invocation.capabilities[0] - assert.ok(invCap.nb) - - return ok(filecoinAcceptResponse) - }), }, }) - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) - - const alice = new Client(await AgentData.create(), { - // @ts-ignore - serviceConf: await mockServiceConf(server), - }) - - const space = await alice.createSpace('test') - const auth = await space.createAuthorization(alice) - await alice.addSpace(auth) - await alice.setCurrentSpace(space.did()) const res = await alice.capability.filecoin.info(cargo.link) - - assert(service.filecoin.info.called) - assert.equal(service.filecoin.info.callCount, 1) - assert(res.out.ok) - assert(res.out.ok.piece.equals(cargo.link)) - assert.equal(res.out.ok.deals.length, 1) - assert(res.out.ok.deals[0].aggregate.equals(aggregate.link)) - assert(res.out.ok.deals[0].aux.dataSource.dealID) - assert(res.out.ok.deals[0].provider) - assert.deepEqual(res.out.ok.aggregates[0].inclusion, { + assert.deepEqual(res.out.ok?.piece.toString(), cargo.link.toString()) + assert.deepEqual( + res.out.ok?.aggregates[0].aggregate.toString(), + aggregate.link.toString() + ) + assert.ok(res.out.ok?.deals.length ?? 0 > 0) + assert.ok(res.out.ok?.deals[0].aggregate.equals(aggregate.link)) + assert.ok(res.out.ok?.deals[0].aux.dataSource.dealID) + assert.ok(res.out.ok?.deals[0].provider) + assert.deepEqual(res.out.ok?.aggregates[0].inclusion, { subtree: proof.ok[0], index: proof.ok[1], }) - }) - }) + }, + }, }) + +Test.test({ FilecoinClient }) diff --git a/packages/w3up-client/test/capability/space.test.js b/packages/w3up-client/test/capability/space.test.js index 179dc0e3a..8e9bcc4b1 100644 --- a/packages/w3up-client/test/capability/space.test.js +++ b/packages/w3up-client/test/capability/space.test.js @@ -1,44 +1,19 @@ -import assert from 'assert' -import { create as createServer, provide } from '@ucanto/server' -import * as CAR from '@ucanto/transport/car' -import * as Signer from '@ucanto/principal/ed25519' -import * as SpaceCapabilities from '@web3-storage/capabilities/space' import { AgentData } from '@web3-storage/access/agent' -import { mockService, mockServiceConf } from '../helpers/mocks.js' import { Client } from '../../src/client.js' -import { validateAuthorization } from '../helpers/utils.js' - -describe('SpaceClient', () => { - describe('info', () => { - it('should retrieve space info', async () => { - const service = mockService({ - space: { - info: provide(SpaceCapabilities.info, ({ invocation }) => { - assert.equal(invocation.issuer.did(), alice.agent.did()) - assert.equal(invocation.capabilities.length, 1) - const invCap = invocation.capabilities[0] - assert.equal(invCap.can, SpaceCapabilities.info.can) - assert.equal(invCap.with, space.did()) - return { - ok: { - did: /** @type {`did:key:${string}`} */ (space.did()), - providers: [], - }, - } - }), - }, - }) - - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) +import * as Test from '../test.js' +export const SpaceClient = Test.withContext({ + info: { + 'should retrieve space info': async ( + assert, + { connection, provisionsStorage } + ) => { const alice = new Client(await AgentData.create(), { // @ts-ignore - serviceConf: await mockServiceConf(server), + serviceConf: { + access: connection, + upload: connection, + }, }) const space = await alice.createSpace('test') @@ -48,14 +23,20 @@ describe('SpaceClient', () => { }) await alice.addSpace(auth) await alice.setCurrentSpace(space.did()) + // Then we setup a billing for this account + await provisionsStorage.put({ + // @ts-expect-error + provider: connection.id.did(), + account: alice.agent.did(), + consumer: space.did(), + }) const info = await alice.capability.space.info(space.did()) - assert(service.space.info.called) - assert.equal(service.space.info.callCount, 1) - assert.equal(info.did, space.did()) - assert.deepEqual(info.providers, []) - }) - }) + assert.deepEqual(info.providers, [connection.id.did()]) + }, + }, }) + +Test.test({ SpaceClient }) diff --git a/packages/w3up-client/test/capability/store.test.js b/packages/w3up-client/test/capability/store.test.js index 800de7b26..602a46602 100644 --- a/packages/w3up-client/test/capability/store.test.js +++ b/packages/w3up-client/test/capability/store.test.js @@ -1,209 +1,147 @@ -import assert from 'assert' -import { create as createServer, provide } from '@ucanto/server' -import * as CAR from '@ucanto/transport/car' -import * as Signer from '@ucanto/principal/ed25519' -import { Store as StoreCapabilities } from '@web3-storage/capabilities' import { AgentData } from '@web3-storage/access/agent' import { randomCAR } from '../helpers/random.js' -import { mockService, mockServiceConf } from '../helpers/mocks.js' import { Client } from '../../src/client.js' -import { validateAuthorization } from '../helpers/utils.js' - -describe('StoreClient', () => { - describe('add', () => { - it('should store a CAR file', async () => { - const service = mockService({ - store: { - add: provide(StoreCapabilities.add, ({ invocation, capability }) => { - assert.equal(invocation.issuer.did(), alice.agent.did()) - assert.equal(invocation.capabilities.length, 1) - const invCap = invocation.capabilities[0] - assert.equal(invCap.can, StoreCapabilities.add.can) - assert.equal(invCap.with, alice.currentSpace()?.did()) - return { - ok: { - status: 'upload', - headers: { 'x-test': 'true' }, - url: 'http://localhost:9200', - link: car.cid, - with: space.did(), - allocated: capability.nb.size, - }, - } - }), - }, - }) - - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) - - const alice = new Client(await AgentData.create(), { - // @ts-ignore - serviceConf: await mockServiceConf(server), - }) - - const space = await alice.createSpace('test') - const auth = await space.createAuthorization(alice) - await alice.addSpace(auth) - await alice.setCurrentSpace(space.did()) - - const car = await randomCAR(128) - const carCID = await alice.capability.store.add(car) - - assert(service.store.add.called) - assert.equal(service.store.add.callCount, 1) - - assert.equal(carCID.toString(), car.cid.toString()) +import * as Test from '../test.js' + +export const StoreClient = Test.withContext({ + 'should store a CAR file': async ( + assert, + { connection, provisionsStorage, storeTable } + ) => { + const alice = new Client(await AgentData.create(), { + // @ts-ignore + serviceConf: { + access: connection, + upload: connection, + }, }) - }) - - describe('list', () => { - it('should list stored CARs', async () => { - const cursor = 'test' - const page = { - cursor, - size: 1, - results: [ - { - link: (await randomCAR(128)).cid, - size: 123, - insertedAt: '1970-01-01T00:00:00.000Z', - }, - ], - } - - const service = mockService({ - store: { - list: provide(StoreCapabilities.list, ({ invocation }) => { - assert.equal(invocation.issuer.did(), alice.agent.did()) - assert.equal(invocation.capabilities.length, 1) - const invCap = invocation.capabilities[0] - assert.equal(invCap.can, StoreCapabilities.list.can) - assert.equal(invCap.with, alice.currentSpace()?.did()) - return { ok: page } - }), - }, - }) - - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) - - const alice = new Client(await AgentData.create(), { - // @ts-ignore - serviceConf: await mockServiceConf(server), - }) - - const space = await alice.createSpace('test') - const auth = await space.createAuthorization(alice) - await alice.addSpace(auth) - await alice.setCurrentSpace(space.did()) - - const res = await alice.capability.store.list() - - assert(service.store.list.called) - assert.equal(service.store.list.callCount, 1) - - assert.equal(res.cursor, cursor) - assert.equal( - res.results[0].link.toString(), - page.results[0].link.toString() - ) + + const space = await alice.createSpace('test') + const auth = await space.createAuthorization(alice) + await alice.addSpace(auth) + await alice.setCurrentSpace(space.did()) + + // Then we setup a billing for this account + await provisionsStorage.put({ + // @ts-expect-error + provider: connection.id.did(), + account: alice.agent.did(), + consumer: space.did(), + }) + + const car = await randomCAR(128) + const carCID = await alice.capability.store.add(car) + + assert.deepEqual(await storeTable.exists(space.did(), car.cid), { + ok: true, + }) + + assert.equal(carCID.toString(), car.cid.toString()) + }, + + 'should list stored CARs': async ( + assert, + { connection, provisionsStorage, storeTable } + ) => { + const alice = new Client(await AgentData.create(), { + // @ts-ignore + serviceConf: { + access: connection, + upload: connection, + }, + }) + + const space = await alice.createSpace('test') + const auth = await space.createAuthorization(alice) + await alice.addSpace(auth) + await alice.setCurrentSpace(space.did()) + + // Then we setup a billing for this account + await provisionsStorage.put({ + // @ts-expect-error + provider: connection.id.did(), + account: alice.agent.did(), + consumer: space.did(), + }) + + const car = await randomCAR(128) + const carCID = await alice.capability.store.add(car) + assert.deepEqual(carCID, car.cid) + + const { + results: [entry], + } = await alice.capability.store.list() + + assert.deepEqual(entry.link, car.cid) + assert.deepEqual(entry.size, car.size) + }, + 'should remove a stored CAR': async ( + assert, + { connection, provisionsStorage } + ) => { + const alice = new Client(await AgentData.create(), { + // @ts-ignore + serviceConf: { + access: connection, + upload: connection, + }, }) - }) - - describe('remove', () => { - it('should remove a stored CAR', async () => { - const service = mockService({ - store: { - remove: provide(StoreCapabilities.remove, ({ invocation }) => { - assert.equal(invocation.issuer.did(), alice.agent.did()) - assert.equal(invocation.capabilities.length, 1) - const invCap = invocation.capabilities[0] - assert.equal(invCap.can, StoreCapabilities.remove.can) - assert.equal(invCap.with, alice.currentSpace()?.did()) - return { ok: { size: 128 } } - }), - }, - }) - - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) - - const alice = new Client(await AgentData.create(), { - // @ts-ignore - serviceConf: await mockServiceConf(server), - }) - - const space = await alice.createSpace('test') - const auth = await space.createAuthorization(alice) - await alice.addSpace(auth) - await alice.setCurrentSpace(space.did()) - - await alice.capability.store.remove((await randomCAR(128)).cid) - - assert(service.store.remove.called) - assert.equal(service.store.remove.callCount, 1) + + const space = await alice.createSpace('test') + const auth = await space.createAuthorization(alice) + await alice.addSpace(auth) + await alice.setCurrentSpace(space.did()) + + // Then we setup a billing for this account + await provisionsStorage.put({ + // @ts-expect-error + provider: connection.id.did(), + account: alice.agent.did(), + consumer: space.did(), }) - }) - - describe('get', () => { - it('should get a stored item', async () => { - const car = await randomCAR(128) - - const service = mockService({ - store: { - get: provide(StoreCapabilities.get, ({ invocation, capability }) => { - assert.equal(invocation.issuer.did(), alice.agent.did()) - assert.equal(invocation.capabilities.length, 1) - assert.equal(capability.can, StoreCapabilities.get.can) - assert.equal(capability.with, alice.currentSpace()?.did()) - return { - ok: { - link: car.cid, - size: car.size, - insertedAt: new Date().toISOString(), - }, - } - }), - }, - }) - - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) - - const alice = new Client(await AgentData.create(), { - // @ts-ignore - serviceConf: await mockServiceConf(server), - }) - - const space = await alice.createSpace('test') - const auth = await space.createAuthorization(alice) - await alice.addSpace(auth) - await alice.setCurrentSpace(space.did()) - - const result = await alice.capability.store.get(car.cid) - - assert(service.store.get.called) - assert.equal(service.store.get.callCount, 1) - - assert.equal(result.link.toString(), car.cid.toString()) - assert.equal(result.size, car.size) + + const car = await randomCAR(128) + const cid = await alice.capability.store.add(car) + + const result = await alice.capability.store.remove(cid) + assert.ok(result.ok) + }, + + 'should get a stored item': async ( + assert, + { connection, provisionsStorage } + ) => { + const car = await randomCAR(128) + + const alice = new Client(await AgentData.create(), { + // @ts-ignore + serviceConf: { + access: connection, + upload: connection, + }, + }) + + const space = await alice.createSpace('test') + const auth = await space.createAuthorization(alice) + await alice.addSpace(auth) + await alice.setCurrentSpace(space.did()) + + // Then we setup a billing for this account + await provisionsStorage.put({ + // @ts-expect-error + provider: connection.id.did(), + account: alice.agent.did(), + consumer: space.did(), }) - }) + + const cid = await alice.capability.store.add(car) + assert.deepEqual(cid, car.cid) + + const result = await alice.capability.store.get(car.cid) + + assert.equal(result.link.toString(), car.cid.toString()) + assert.equal(result.size, car.size) + }, }) + +Test.test({ StoreClient }) diff --git a/packages/w3up-client/test/capability/subscription.test.js b/packages/w3up-client/test/capability/subscription.test.js index e6e8c86c5..b3d8f1408 100644 --- a/packages/w3up-client/test/capability/subscription.test.js +++ b/packages/w3up-client/test/capability/subscription.test.js @@ -1,102 +1,44 @@ -import assert from 'assert' -import { create as createServer, provide } from '@ucanto/server' -import * as CAR from '@ucanto/transport/car' -import * as Signer from '@ucanto/principal/ed25519' -import { Absentee } from '@ucanto/principal' -import * as SubscriptionCapabilities from '@web3-storage/capabilities/subscription' -import { AgentData } from '@web3-storage/access/agent' -import { mockService, mockServiceConf } from '../helpers/mocks.js' -import { Client } from '../../src/client.js' -import { createAuthorization, validateAuthorization } from '../helpers/utils.js' - -describe('SubscriptionClient', () => { - describe('list', () => { - it('should list subscriptions', async () => { - const space = await Signer.generate() - /** @type {import('@web3-storage/capabilities/types').SubscriptionListItem} */ - const subscription = { - provider: 'did:web:web3.storage', - subscription: 'test', - consumers: [space.did()], - } - const account = Absentee.from({ id: 'did:mailto:example.com:alice' }) - const service = mockService({ - subscription: { - list: provide(SubscriptionCapabilities.list, ({ capability }) => { - assert.equal(capability.with, account.did()) - return { - ok: { - results: [subscription], - }, - } - }), - }, - }) - - const serviceSigner = await Signer.generate() - const server = createServer({ - id: serviceSigner, - service, - codec: CAR.inbound, - validateAuthorization, - }) - - const alice = new Client(await AgentData.create(), { - // @ts-ignore - serviceConf: await mockServiceConf(server), - }) - - const auths = await createAuthorization({ - account, - service: serviceSigner, - agent: alice.agent.issuer, - }) - await alice.agent.addProofs(auths) - - const subs = await alice.capability.subscription.list(account.did()) - - assert(service.subscription.list.called) - assert.equal(service.subscription.list.callCount, 1) - assert.deepEqual(subs, { results: [subscription] }) - }) - - it('should throw on service failure', async () => { - const account = Absentee.from({ id: 'did:mailto:example.com:alice' }) - const service = mockService({ - subscription: { - list: provide(SubscriptionCapabilities.list, ({ capability }) => { - assert.equal(capability.with, account.did()) - return { error: new Error('boom') } - }), - }, - }) - - const serviceSigner = await Signer.generate() - const server = createServer({ - id: serviceSigner, - service, - codec: CAR.inbound, - validateAuthorization, - }) - - const alice = new Client(await AgentData.create(), { - // @ts-ignore - serviceConf: await mockServiceConf(server), - }) - - const auths = await createAuthorization({ - account, - service: serviceSigner, - agent: alice.agent.issuer, - }) - await alice.agent.addProofs(auths) - - await assert.rejects(alice.capability.subscription.list(account.did()), { - message: 'failed subscription/list invocation', - }) - - assert(service.subscription.list.called) - assert.equal(service.subscription.list.callCount, 1) - }) - }) +import * as Test from '../test.js' +import * as Account from '../../src/account.js' +import * as Result from '../../src/result.js' + +export const SubscriptionClient = Test.withContext({ + list: { + 'should list subscriptions': async ( + assert, + { client, connection, service, plansStorage, grantAccess, mail } + ) => { + const space = await client.createSpace('test') + const email = 'alice@web.mail' + const login = Account.login(client, email) + const message = await mail.take() + assert.deepEqual(message.to, email) + await grantAccess(message) + const account = Result.try(await login) + await account.save() + + assert.deepEqual( + await client.capability.subscription.list(account.did()), + { results: [] } + ) + + const result = await account.provision(space.did()) + assert.ok(result.ok) + + assert.deepEqual( + await client.capability.subscription.list(account.did()), + { + results: [ + { + provider: connection.id.did(), + consumers: [space.did()], + subscription: `${account.did()}:${space.did()}@${connection.id.did()}`, + }, + ], + } + ) + }, + }, }) + +Test.test({ SubscriptionClient }) diff --git a/packages/w3up-client/test/capability/upload.test.js b/packages/w3up-client/test/capability/upload.test.js index 15b151beb..5b9a10e6c 100644 --- a/packages/w3up-client/test/capability/upload.test.js +++ b/packages/w3up-client/test/capability/upload.test.js @@ -1,217 +1,130 @@ -import assert from 'assert' -import { create as createServer, provide } from '@ucanto/server' -import * as CAR from '@ucanto/transport/car' -import * as Signer from '@ucanto/principal/ed25519' -import { Upload as UploadCapabilities } from '@web3-storage/capabilities' -import { AgentData } from '@web3-storage/access/agent' import { randomCAR } from '../helpers/random.js' -import { mockService, mockServiceConf } from '../helpers/mocks.js' -import { Client } from '../../src/client.js' -import { validateAuthorization } from '../helpers/utils.js' - -describe('UploadClient', () => { - describe('add', () => { - it('should register an upload', async () => { +import * as Test from '../test.js' + +export const UploadClient = Test.withContext({ + add: { + 'should register an upload': async ( + assert, + { client: alice, service, provisionsStorage, uploadTable } + ) => { const car = await randomCAR(128) - const res = { - root: car.roots[0], - shards: [car.cid], - } - - const service = mockService({ - upload: { - add: provide(UploadCapabilities.add, ({ invocation }) => { - assert.equal(invocation.issuer.did(), alice.agent.did()) - assert.equal(invocation.capabilities.length, 1) - const invCap = invocation.capabilities[0] - assert.equal(invCap.can, UploadCapabilities.add.can) - assert.equal(invCap.with, alice.currentSpace()?.did()) - assert.equal(String(invCap.nb?.root), car.roots[0].toString()) - assert.equal(invCap.nb?.shards?.length, 1) - assert.equal(String(invCap.nb?.shards?.[0]), car.cid.toString()) - return { ok: res } - }), - }, - }) - - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) - - const alice = new Client(await AgentData.create(), { - // @ts-ignore - serviceConf: await mockServiceConf(server), - }) - const space = await alice.createSpace('test') const auth = await space.createAuthorization(alice) await alice.addSpace(auth) await alice.setCurrentSpace(space.did()) - await alice.capability.upload.add(car.roots[0], [car.cid]) - - assert(service.upload.add.called) - assert.equal(service.upload.add.callCount, 1) - }) - }) - - describe('list', () => { - it('should list uploads', async () => { - const car = await randomCAR(128) - const cursor = 'test' - const page = { - cursor, - size: 1, - results: [ - { - root: car.roots[0], - shards: [car.cid], - insertedAt: '1970-01-01T00:00:00.000Z', - updatedAt: '1970-01-01T00:00:00.000Z', - }, - ], - } - - const service = mockService({ - upload: { - list: provide(UploadCapabilities.list, ({ invocation }) => { - assert.equal(invocation.issuer.did(), alice.agent.did()) - assert.equal(invocation.capabilities.length, 1) - const invCap = invocation.capabilities[0] - assert.equal(invCap.can, UploadCapabilities.list.can) - assert.equal(invCap.with, alice.currentSpace()?.did()) - return { ok: page } - }), - }, + // @ts-expect-error + await provisionsStorage.put({ + provider: service.did(), + customer: 'did:mailto:alice@web.mail', + consumer: space.did(), }) - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) + const result = await alice.capability.upload.add(car.roots[0], [car.cid]) + assert.deepEqual(result.root, car.roots[0]) + assert.deepEqual(result.shards, [car.cid]) - const alice = new Client(await AgentData.create(), { - // @ts-ignore - serviceConf: await mockServiceConf(server), + assert.deepEqual(await uploadTable.exists(space.did(), car.roots[0]), { + ok: true, }) + }, + }, + + list: { + 'should list uploads': async ( + assert, + { client: alice, service, provisionsStorage } + ) => { + const car = await randomCAR(128) const space = await alice.createSpace('test') const auth = await space.createAuthorization(alice) await alice.addSpace(auth) await alice.setCurrentSpace(space.did()) - const res = await alice.capability.upload.list() + // @ts-expect-error + await provisionsStorage.put({ + provider: service.did(), + customer: 'did:mailto:alice@web.mail', + consumer: space.did(), + }) - assert(service.upload.list.called) - assert.equal(service.upload.list.callCount, 1) + assert.deepEqual(await alice.capability.upload.list(), { + results: [], + size: 0, + }) - assert.equal(res.cursor, cursor) - assert.equal( - res.results[0].root.toString(), - page.results[0].root.toString() - ) - }) - }) + await alice.capability.upload.add(car.roots[0], [car.cid]) - describe('remove', () => { - it('should remove an upload', async () => { - const car = await randomCAR(128) + const { + results: [entry], + } = await alice.capability.upload.list() - const service = mockService({ - upload: { - remove: provide(UploadCapabilities.remove, ({ invocation }) => { - assert.equal(invocation.issuer.did(), alice.agent.did()) - assert.equal(invocation.capabilities.length, 1) - const invCap = invocation.capabilities[0] - assert.equal(invCap.can, UploadCapabilities.remove.can) - assert.equal(invCap.with, alice.currentSpace()?.did()) - return { - ok: { - root: car.roots[0], - shards: [car.cid], - }, - } - }), - }, - }) + assert.deepEqual(entry.root, car.roots[0]) + assert.deepEqual(entry.shards, [car.cid]) + }, + }, - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) - - const alice = new Client(await AgentData.create(), { - // @ts-ignore - serviceConf: await mockServiceConf(server), - }) + remove: { + 'should remove an upload': async ( + assert, + { client: alice, uploadTable, provisionsStorage, service } + ) => { + const car = await randomCAR(128) const space = await alice.createSpace('test') const auth = await space.createAuthorization(alice) await alice.addSpace(auth) await alice.setCurrentSpace(space.did()) - await alice.capability.upload.remove(car.roots[0]) - - assert(service.upload.remove.called) - assert.equal(service.upload.remove.callCount, 1) - }) - }) - - describe('get', () => { - it('should get an upload', async () => { - const car = await randomCAR(128) - - const service = mockService({ - upload: { - get: provide(UploadCapabilities.get, ({ invocation, capability }) => { - assert.equal(invocation.issuer.did(), alice.agent.did()) - assert.equal(invocation.capabilities.length, 1) - assert.equal(capability.can, UploadCapabilities.get.can) - assert.equal(capability.with, alice.currentSpace()?.did()) - return { - ok: { - root: car.roots[0], - shards: [car.cid], - insertedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }, - } - }), - }, + // @ts-expect-error + await provisionsStorage.put({ + provider: service.did(), + customer: 'did:mailto:alice@web.mail', + consumer: space.did(), }) - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, + await alice.capability.upload.add(car.roots[0], [car.cid]) + assert.deepEqual(await uploadTable.exists(space.did(), car.roots[0]), { + ok: true, }) - const alice = new Client(await AgentData.create(), { - // @ts-ignore - serviceConf: await mockServiceConf(server), + await alice.capability.upload.remove(car.roots[0]) + + assert.deepEqual(await uploadTable.exists(space.did(), car.roots[0]), { + ok: false, }) + }, + }, + + get: { + 'should get an upload': async ( + assert, + { client: alice, service, provisionsStorage } + ) => { + const car = await randomCAR(128) const space = await alice.createSpace('test') const auth = await space.createAuthorization(alice) await alice.addSpace(auth) await alice.setCurrentSpace(space.did()) - const result = await alice.capability.upload.get(car.cid) + // @ts-expect-error + await provisionsStorage.put({ + provider: service.did(), + customer: 'did:mailto:alice@web.mail', + consumer: space.did(), + }) + + await alice.capability.upload.add(car.roots[0], [car.cid]) - assert(service.upload.get.called) - assert.equal(service.upload.get.callCount, 1) + const result = await alice.capability.upload.get(car.roots[0]) - assert.equal(result.root.toString(), car.roots[0].toString()) - assert.equal(result.shards?.[0].toString(), car.cid) - }) - }) + assert.deepEqual(result.root, car.roots[0]) + assert.deepEqual(result.shards, [car.cid]) + }, + }, }) + +Test.test({ UploadClient }) diff --git a/packages/w3up-client/test/capability/usage.test.js b/packages/w3up-client/test/capability/usage.test.js index d814c05dd..4a653ef6e 100644 --- a/packages/w3up-client/test/capability/usage.test.js +++ b/packages/w3up-client/test/capability/usage.test.js @@ -1,96 +1,70 @@ -import assert from 'assert' -import { create as createServer, provide } from '@ucanto/server' -import * as CAR from '@ucanto/transport/car' -import * as Signer from '@ucanto/principal/ed25519' -import * as UsageCapabilities from '@web3-storage/capabilities/usage' import { AgentData } from '@web3-storage/access/agent' -import { mockService, mockServiceConf } from '../helpers/mocks.js' import { Client } from '../../src/client.js' -import { validateAuthorization } from '../helpers/utils.js' - -describe('UsageClient', () => { - describe('report', () => { - it('should fetch usage report', async () => { - const service = mockService({ - usage: { - report: provide(UsageCapabilities.report, () => { - return { ok: { [report.provider]: report } } - }), - }, - }) - - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) +import * as Test from '../test.js' +export const UsageClient = Test.withContext({ + report: { + 'should fetch usage report': async ( + assert, + { connection, provisionsStorage, uploadTable } + ) => { const alice = new Client(await AgentData.create(), { // @ts-ignore - serviceConf: await mockServiceConf(server), + serviceConf: { + access: connection, + upload: connection, + }, }) const space = await alice.createSpace('test') const auth = await space.createAuthorization(alice) await alice.addSpace(auth) - const period = { from: new Date(0), to: new Date() } - /** @type {import('@web3-storage/capabilities/types').UsageData} */ - const report = { - provider: 'did:web:web3.storage', - space: space.did(), - size: { initial: 0, final: 0 }, - period: { - from: period.from.toISOString(), - to: period.to.toISOString(), - }, - events: [], - } + // Then we setup a billing for this account + await provisionsStorage.put({ + // @ts-expect-error + provider: connection.id.did(), + account: alice.agent.did(), + consumer: space.did(), + }) - const subs = await alice.capability.usage.report(space.did(), period) + const content = new Blob(['hello world']) + await alice.uploadFile(content) - assert(service.usage.report.called) - assert.equal(service.usage.report.callCount, 1) - assert.deepEqual(subs, { [report.provider]: report }) - }) + const period = { from: new Date(0), to: new Date() } - it('should throw on service failure', async () => { - const service = mockService({ - usage: { - report: provide(UsageCapabilities.report, ({ capability }) => { - return { error: new Error('boom') } - }), - }, - }) + const report = await alice.capability.usage.report(space.did(), period) - const serviceSigner = await Signer.generate() - const server = createServer({ - id: serviceSigner, - service, - codec: CAR.inbound, - validateAuthorization, - }) + const [[id, record]] = Object.entries(report) + assert.equal(id, connection.id.did()) + + assert.equal(record.provider, connection.id.did()) + assert.equal(record.space, space.did()) + assert.equal(record.period.from, period.from.toISOString()) + assert.ok(record.period.to > period.to.toISOString()) + assert.equal(record.size.initial, 0) + assert.ok(record.size.final >= content.size) + assert.ok(record.events.length > 0) + }, + 'should be empty on unknown space': async (assert, { connection }) => { const alice = new Client(await AgentData.create(), { // @ts-ignore - serviceConf: await mockServiceConf(server), + serviceConf: { + access: connection, + upload: connection, + }, }) const space = await alice.createSpace('test') const auth = await space.createAuthorization(alice) await alice.addSpace(auth) - await assert.rejects( - () => { - const period = { from: new Date(), to: new Date() } - return alice.capability.usage.report(space.did(), period) - }, - { message: 'failed usage/report invocation' } - ) - - assert(service.usage.report.called) - assert.equal(service.usage.report.callCount, 1) - }) - }) + const period = { from: new Date(), to: new Date() } + const report = await alice.capability.usage.report(space.did(), period) + assert.deepEqual(report, {}) + }, + }, }) + +Test.test({ UsageClient }) diff --git a/packages/w3up-client/test/client-accounts.test.js b/packages/w3up-client/test/client-accounts.test.js index c627f6805..8515debd0 100644 --- a/packages/w3up-client/test/client-accounts.test.js +++ b/packages/w3up-client/test/client-accounts.test.js @@ -4,7 +4,7 @@ import * as Account from '../src/account.js' /** * @type {Test.Suite} */ -export const testClientAccounts = { +export const testClientAccounts = Test.withContext({ 'list accounts': async (assert, { client, mail, grantAccess }) => { const email = 'alice@web.mail' @@ -32,6 +32,6 @@ export const testClientAccounts = { assert.equal(account.did(), Account.fromEmail(email)) assert.equal(account.proofs.length, 2) }, -} +}) Test.test({ 'Client accounts': testClientAccounts }) diff --git a/packages/w3up-client/test/client.test.js b/packages/w3up-client/test/client.test.js index be37a3fb6..7c76a8fbf 100644 --- a/packages/w3up-client/test/client.test.js +++ b/packages/w3up-client/test/client.test.js @@ -1,102 +1,31 @@ import assert from 'assert' -import { - Delegation, - create as createServer, - parseLink, - provide, - provideAdvanced, - error, -} from '@ucanto/server' -import * as CAR from '@ucanto/transport/car' -import * as Signer from '@ucanto/principal/ed25519' -import * as StoreCapabilities from '@web3-storage/capabilities/store' -import * as UploadCapabilities from '@web3-storage/capabilities/upload' -import * as UCANCapabilities from '@web3-storage/capabilities/ucan' -import * as StorefrontCapabilities from '@web3-storage/capabilities/filecoin/storefront' -import { Piece } from '@web3-storage/data-segment' +import { parseLink } from '@ucanto/server' import { AgentData } from '@web3-storage/access/agent' -import { StoreItemNotFound } from '../../upload-api/src/store/lib.js' import { randomBytes, randomCAR } from './helpers/random.js' import { toCAR } from './helpers/car.js' -import { mockService, mockServiceConf } from './helpers/mocks.js' import { File } from './helpers/shims.js' import { Client } from '../src/client.js' -import { validateAuthorization } from './helpers/utils.js' -import { getFilecoinOfferResponse } from './helpers/filecoin.js' - -describe('Client', () => { - describe('uploadFile', () => { - it('should upload a file to the service', async () => { +import * as Test from './test.js' + +/** @type {Test.Suite} */ +export const testClient = { + uploadFile: Test.withContext({ + 'should upload a file to the service': async ( + assert, + { connection, provisionsStorage, uploadTable, storeTable } + ) => { const bytes = await randomBytes(128) const file = new Blob([bytes]) const expectedCar = await toCAR(bytes) - const piece = Piece.fromPayload(bytes).link /** @type {import('@web3-storage/upload-client/types').CARLink|undefined} */ let carCID - const service = mockService({ - store: { - add: provide(StoreCapabilities.add, ({ invocation, capability }) => { - assert.equal(invocation.issuer.did(), alice.agent.did()) - assert.equal(invocation.capabilities.length, 1) - assert.equal(capability.can, StoreCapabilities.add.can) - assert.equal(capability.with, alice.currentSpace()?.did()) - - return { - ok: { - status: 'upload', - headers: { 'x-test': 'true' }, - url: 'http://localhost:9200', - link: /** @type {import('@web3-storage/upload-client/types').CARLink} */ ( - invocation.capabilities[0].nb?.link - ), - with: space.did(), - allocated: capability.nb.size, - }, - } - }), - }, - filecoin: { - offer: provideAdvanced({ - capability: StorefrontCapabilities.filecoinOffer, - handler: async ({ invocation, context }) => { - const invCap = invocation.capabilities[0] - if (!invCap.nb) { - throw new Error('no params received') - } - return getFilecoinOfferResponse(context.id, piece, invCap.nb) - }, - }), - }, - upload: { - add: provide(UploadCapabilities.add, ({ invocation }) => { - assert.equal(invocation.issuer.did(), alice.agent.did()) - assert.equal(invocation.capabilities.length, 1) - const invCap = invocation.capabilities[0] - assert.equal(invCap.can, UploadCapabilities.add.can) - assert.equal(invCap.with, alice.currentSpace()?.did()) - assert.equal(invCap.nb?.shards?.length, 1) - assert.equal(String(invCap.nb?.shards?.[0]), carCID?.toString()) - return { - ok: { - root: expectedCar.roots[0], - shards: [expectedCar.cid], - }, - } - }), - }, - }) - - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) - const alice = new Client(await AgentData.create(), { // @ts-ignore - serviceConf: await mockServiceConf(server), + serviceConf: { + access: connection, + upload: connection, + }, }) const space = await alice.createSpace('upload-test') @@ -104,23 +33,43 @@ describe('Client', () => { await alice.addSpace(auth) await alice.setCurrentSpace(space.did()) + // Then we setup a billing for this account + await provisionsStorage.put({ + // @ts-expect-error + provider: connection.id.did(), + account: alice.agent.did(), + consumer: space.did(), + }) + const dataCID = await alice.uploadFile(file, { onShardStored: (meta) => { carCID = meta.cid }, }) - assert(service.store.add.called) - assert.equal(service.store.add.callCount, 1) - assert(service.upload.add.called) - assert.equal(service.upload.add.callCount, 1) + assert.deepEqual(await uploadTable.exists(space.did(), dataCID), { + ok: true, + }) + + assert.deepEqual(await storeTable.exists(space.did(), expectedCar.cid), { + ok: true, + }) assert.equal(carCID?.toString(), expectedCar.cid.toString()) assert.equal(dataCID.toString(), expectedCar.roots[0].toString()) - }) + }, - it('should not allow upload without a current space', async () => { - const alice = new Client(await AgentData.create()) + 'should not allow upload without a current space': async ( + assert, + { connection } + ) => { + const alice = new Client(await AgentData.create(), { + // @ts-ignore + serviceConf: { + access: connection, + upload: connection, + }, + }) const bytes = await randomBytes(128) const file = new Blob([bytes]) @@ -129,79 +78,28 @@ describe('Client', () => { message: 'missing current space: use createSpace() or setCurrentSpace()', }) - }) - }) + }, + }), - describe('uploadDirectory', () => { - it('should upload a directory to the service', async () => { + uploadDirectory: Test.withContext({ + 'should upload a directory to the service': async ( + assert, + { connection, provisionsStorage, uploadTable } + ) => { const bytesList = [await randomBytes(128), await randomBytes(32)] const files = bytesList.map( (bytes, index) => new File([bytes], `${index}.txt`) ) - const pieces = bytesList.map((bytes) => Piece.fromPayload(bytes).link) /** @type {import('@web3-storage/upload-client/types').CARLink|undefined} */ let carCID - const service = mockService({ - store: { - add: provide(StoreCapabilities.add, ({ invocation, capability }) => { - assert.equal(invocation.issuer.did(), alice.agent.did()) - assert.equal(invocation.capabilities.length, 1) - assert.equal(capability.can, StoreCapabilities.add.can) - assert.equal(capability.with, alice.currentSpace()?.did()) - return { - ok: { - status: 'upload', - headers: { 'x-test': 'true' }, - url: 'http://localhost:9200', - link: /** @type {import('@web3-storage/upload-client/types').CARLink} */ ( - invocation.capabilities[0].nb?.link - ), - with: space.did(), - allocated: capability.nb.size, - }, - } - }), - }, - filecoin: { - offer: provideAdvanced({ - capability: StorefrontCapabilities.filecoinOffer, - handler: async ({ invocation, context }) => { - const invCap = invocation.capabilities[0] - if (!invCap.nb) { - throw new Error('no params received') - } - return getFilecoinOfferResponse(context.id, pieces[0], invCap.nb) - }, - }), - }, - upload: { - add: provide(UploadCapabilities.add, ({ invocation }) => { - assert.equal(invocation.issuer.did(), alice.agent.did()) - assert.equal(invocation.capabilities.length, 1) - const invCap = invocation.capabilities[0] - assert.equal(invCap.can, UploadCapabilities.add.can) - assert.equal(invCap.with, alice.currentSpace()?.did()) - assert.equal(invCap.nb?.shards?.length, 1) - if (!invCap.nb) throw new Error('nb must be present') - return { - ok: invCap.nb, - } - }), - }, - }) - - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) - const alice = new Client(await AgentData.create(), { // @ts-ignore - serviceConf: await mockServiceConf(server), + serviceConf: { + access: connection, + upload: connection, + }, }) const space = await alice.createSpace('upload-dir-test') @@ -210,145 +108,114 @@ describe('Client', () => { await alice.setCurrentSpace(space.did()) + // Then we setup a billing for this account + await provisionsStorage.put({ + // @ts-expect-error + provider: connection.id.did(), + account: alice.agent.did(), + consumer: space.did(), + }) + const dataCID = await alice.uploadDirectory(files, { onShardStored: (meta) => { carCID = meta.cid }, }) - assert(service.store.add.called) - assert.equal(service.store.add.callCount, 1) - assert(service.upload.add.called) - assert.equal(service.upload.add.callCount, 1) - - assert(carCID) - assert(dataCID) - }) - }) - - describe('uploadCAR', () => { - it('uploads a CAR file to the service', async () => { - const car = await randomCAR(32) - const someBytes = new Uint8Array(await car.arrayBuffer()) - const piece = Piece.fromPayload(someBytes).link - - /** @type {import('../src/types.js').CARLink?} */ - let carCID - - const service = mockService({ - store: { - add: provide(StoreCapabilities.add, ({ invocation, capability }) => { - assert.equal(invocation.issuer.did(), alice.agent.did()) - assert.equal(invocation.capabilities.length, 1) - assert.equal(capability.can, StoreCapabilities.add.can) - assert.equal(capability.with, space.did()) - return { - ok: { - status: 'upload', - headers: { 'x-test': 'true' }, - url: 'http://localhost:9200', - link: /** @type {import('@web3-storage/upload-client/types').CARLink} */ ( - invocation.capabilities[0].nb?.link - ), - with: space.did(), - allocated: capability.nb.size, - }, - } - }), - }, - filecoin: { - offer: provideAdvanced({ - capability: StorefrontCapabilities.filecoinOffer, - handler: async ({ invocation, context }) => { - const invCap = invocation.capabilities[0] - if (!invCap.nb) { - throw new Error('no params received') - } - return getFilecoinOfferResponse(context.id, piece, invCap.nb) - }, - }), - }, - upload: { - add: provide(UploadCapabilities.add, ({ invocation }) => { - assert.equal(invocation.issuer.did(), alice.agent.did()) - assert.equal(invocation.capabilities.length, 1) - const invCap = invocation.capabilities[0] - assert.equal(invCap.can, UploadCapabilities.add.can) - assert.equal(invCap.with, space.did()) - if (!invCap.nb) throw new Error('nb must be present') - assert.equal(invCap.nb.shards?.length, 1) - assert.ok(carCID) - assert.equal(invCap.nb.shards?.[0].toString(), carCID.toString()) - return { - ok: invCap.nb, - } - }), - }, + assert.deepEqual(await uploadTable.exists(space.did(), dataCID), { + ok: true, }) + assert.ok(carCID) + assert.ok(dataCID) + }, + }), + uploadCar: Test.withContext({ + 'uploads a CAR file to the service': async ( + assert, + { connection, provisionsStorage, uploadTable, storeTable } + ) => { + const car = await randomCAR(32) - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) + /** @type {import('../src/types.js').CARLink|null} */ + let carCID = null const alice = new Client(await AgentData.create(), { // @ts-ignore - serviceConf: await mockServiceConf(server), + serviceConf: { + access: connection, + upload: connection, + }, }) const space = await alice.createSpace('car-space') await alice.addSpace(await space.createAuthorization(alice)) await alice.setCurrentSpace(space.did()) - await alice.uploadCAR(car, { + + // Then we setup a billing for this account + await provisionsStorage.put({ + // @ts-expect-error + provider: connection.id.did(), + account: alice.agent.did(), + consumer: space.did(), + }) + + const root = await alice.uploadCAR(car, { onShardStored: (meta) => { carCID = meta.cid }, }) - assert(service.store.add.called) - assert.equal(service.store.add.callCount, 1) - assert(service.upload.add.called) - assert.equal(service.upload.add.callCount, 1) - }) - }) + assert.deepEqual(await uploadTable.exists(space.did(), root), { + ok: true, + }) + + if (carCID == null) { + return assert.ok(carCID) + } - describe('getReceipt', () => { - it('should find a receipt', async () => { + assert.deepEqual(await storeTable.exists(space.did(), carCID), { + ok: true, + }) + }, + }), + getRecipt: Test.withContext({ + 'should find a receipt': async (assert, { connection }) => { const taskCid = parseLink( 'bafyreibo6nqtvp67daj7dkmeb5c2n6bg5bunxdmxq3lghtp3pmjtzpzfma' ) const alice = new Client(await AgentData.create(), { receiptsEndpoint: new URL('http://localhost:9201'), + // @ts-ignore + serviceConf: { + access: connection, + upload: connection, + }, }) const receipt = await alice.getReceipt(taskCid) // This is a real `piece/accept` receipt exported as fixture - assert(receipt) - assert.ok(receipt.ran.link().equals(taskCid)) - assert.ok(receipt.out.ok) - }) - }) - - describe('currentSpace', () => { - it('should return undefined or space', async () => { + assert.ok(receipt) + assert.ok(receipt?.ran.link().equals(taskCid)) + assert.ok(receipt?.out.ok) + }, + }), + currentSpace: { + 'should return undefined or space': async (assert) => { const alice = new Client(await AgentData.create()) const current0 = alice.currentSpace() - assert(current0 === undefined) + assert.equal(current0, undefined) const space = await alice.createSpace('new-space') await alice.addSpace(await space.createAuthorization(alice)) await alice.setCurrentSpace(space.did()) const current1 = alice.currentSpace() - assert(current1) - assert.equal(current1.did(), space.did()) - }) - }) - - describe('spaces', () => { - it('should get agent spaces', async () => { + assert.ok(current1) + assert.equal(current1?.did(), space.did()) + }, + }, + spaces: { + 'should get agent spaces': async (assert) => { const alice = new Client(await AgentData.create()) const name = `space-${Date.now()}` @@ -360,9 +227,9 @@ describe('Client', () => { assert.equal(spaces.length, 1) assert.equal(spaces[0].did(), space.did()) assert.equal(spaces[0].name, name) - }) + }, - it('should add space', async () => { + 'should add space': async () => { const alice = new Client(await AgentData.create()) const bob = new Client(await AgentData.create()) @@ -384,11 +251,10 @@ describe('Client', () => { const spaces = bob.spaces() assert.equal(spaces.length, 1) assert.equal(spaces[0].did(), space.did()) - }) - }) - - describe('proofs', () => { - it('should get proofs', async () => { + }, + }, + proofs: { + 'should get proofs': async (assert) => { const alice = new Client(await AgentData.create()) const bob = new Client(await AgentData.create()) @@ -403,11 +269,10 @@ describe('Client', () => { const proofs = bob.proofs() assert.equal(proofs.length, 1) assert.equal(proofs[0].cid.toString(), delegation.cid.toString()) - }) - }) - - describe('delegations', () => { - it('should get delegations', async () => { + }, + }, + delegations: { + 'should get delegations': async (assert) => { const alice = new Client(await AgentData.create()) const bob = new Client(await AgentData.create()) @@ -427,48 +292,24 @@ describe('Client', () => { assert.equal(delegations.length, 1) assert.equal(delegations[0].cid.toString(), delegation.cid.toString()) assert.equal(delegations[0].meta()?.audience?.name, name) - }) - }) - - describe('revokeDelegation', () => { - it('should revoke a delegation by CID', async () => { - const service = mockService({ - ucan: { - revoke: provide( - UCANCapabilities.revoke, - ({ capability, invocation }) => { - // copy a bit of the production revocation handler to do basic validation - const { nb: input } = capability - const ucan = Delegation.view( - { root: input.ucan, blocks: invocation.blocks }, - null - ) - return ucan - ? { ok: { time: Date.now() } } - : { - error: { - name: 'UCANNotFound', - message: 'Could not find delegation in invocation blocks', - }, - } - } - ), - }, - }) + }, + }, - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) + revokeDelegation: Test.withContext({ + 'should revoke a delegation by CID': async (assert, { connection }) => { const alice = new Client(await AgentData.create(), { // @ts-ignore - serviceConf: await mockServiceConf(server), + serviceConf: { + access: connection, + upload: connection, + }, }) const bob = new Client(await AgentData.create(), { // @ts-ignore - serviceConf: await mockServiceConf(server), + serviceConf: { + access: connection, + upload: connection, + }, }) const space = await alice.createSpace('test') @@ -485,9 +326,11 @@ describe('Client', () => { const result = await alice.revokeDelegation(delegation.cid) assert.ok(result.ok) - }) + }, - it('should fail to revoke a delegation it does not know about', async () => { + 'should fail to revoke a delegation it does not know about': async ( + assert + ) => { const alice = new Client(await AgentData.create()) const bob = new Client(await AgentData.create()) @@ -501,18 +344,17 @@ describe('Client', () => { const result = await bob.revokeDelegation(delegation.cid) assert.ok(result.error, 'revoke succeeded when it should not have') - }) - }) - - describe('defaultProvider', () => { - it('should return the connection ID', async () => { + }, + }), + defaultProvider: { + 'should return the connection ID': async (assert) => { const alice = new Client(await AgentData.create()) assert.equal(alice.defaultProvider(), 'did:web:web3.storage') - }) - }) + }, + }, - describe('capability', () => { - it('should allow typed access to capability specific clients', async () => { + capability: { + 'should allow typed access to capability specific clients': async () => { const client = new Client(await AgentData.create()) assert.equal(typeof client.capability.access.authorize, 'function') assert.equal(typeof client.capability.access.claim, 'function') @@ -523,53 +365,22 @@ describe('Client', () => { assert.equal(typeof client.capability.upload.add, 'function') assert.equal(typeof client.capability.upload.list, 'function') assert.equal(typeof client.capability.upload.remove, 'function') - }) - }) - - describe('remove', () => { - it('should remove an uploaded file from the service with its shards', async () => { + }, + }, + + remove: Test.withContext({ + 'should remove an uploaded file from the service with its shards': async ( + assert, + { connection, provisionsStorage, uploadTable } + ) => { const bytes = await randomBytes(128) - const uploadedCar = await toCAR(bytes) - const contentCID = uploadedCar.roots[0] - - const service = mockService({ - store: { - remove: provide(StoreCapabilities.remove, ({ invocation }) => { - return { ok: { size: uploadedCar.size } } - }), - }, - upload: { - get: provide(UploadCapabilities.get, ({ invocation }) => { - return { - ok: { - root: uploadedCar.roots[0], - shards: [uploadedCar.cid], - insertedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }, - } - }), - remove: provide(UploadCapabilities.remove, ({ invocation }) => { - return { - ok: { - root: uploadedCar.roots[0], - shards: [uploadedCar.cid], - }, - } - }), - }, - }) - - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) const alice = new Client(await AgentData.create(), { // @ts-ignore - serviceConf: await mockServiceConf(server), + serviceConf: { + access: connection, + upload: connection, + }, }) // setup space @@ -578,99 +389,96 @@ describe('Client', () => { await alice.addSpace(auth) await alice.setCurrentSpace(space.did()) - await assert.doesNotReject(() => - alice.remove(contentCID, { shards: true }) - ) + // Then we setup a billing for this account + await provisionsStorage.put({ + // @ts-expect-error + provider: connection.id.did(), + account: alice.agent.did(), + consumer: space.did(), + }) - assert(service.upload.get.called) - assert.equal(service.upload.get.callCount, 1) - assert(service.upload.remove.called) - assert.equal(service.upload.remove.callCount, 1) - assert(service.store.remove.called) - assert.equal(service.store.remove.callCount, 1) - }) + const root = await alice.uploadFile(new Blob([bytes])) - it('should remove an uploaded file from the service without its shards by default', async () => { - const bytes = await randomBytes(128) - const uploadedCar = await toCAR(bytes) - const contentCID = uploadedCar.roots[0] + assert.deepEqual(await uploadTable.exists(space.did(), root), { + ok: true, + }) - const service = mockService({ - upload: { - get: provide(UploadCapabilities.get, ({ invocation }) => { - return { - ok: { - root: uploadedCar.roots[0], - shards: [uploadedCar.cid], - insertedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }, - } - }), - remove: provide(UploadCapabilities.remove, ({ invocation }) => { - return { - ok: { - root: uploadedCar.roots[0], - shards: [uploadedCar.cid], - }, - } + assert.deepEqual( + await alice + .remove(root, { shards: true }) + .then((ok) => ({ ok: {} })) + .catch((error) => { + error }), - }, - store: { - remove: provide(StoreCapabilities.remove, ({ invocation }) => { - return { ok: { size: uploadedCar.size } } - }), - }, - }) + { ok: {} } + ) - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, + assert.deepEqual(await uploadTable.exists(space.did(), root), { + ok: false, }) + }, - const alice = new Client(await AgentData.create(), { - // @ts-ignore - serviceConf: await mockServiceConf(server), - }) + 'should remove an uploaded file from the service without its shards by default': + async (assert, { connection, provisionsStorage, uploadTable }) => { + const bytes = await randomBytes(128) - // setup space - const space = await alice.createSpace('upload-test') - const auth = await space.createAuthorization(alice) - await alice.addSpace(auth) - await alice.setCurrentSpace(space.did()) + const alice = new Client(await AgentData.create(), { + // @ts-ignore + serviceConf: { + access: connection, + upload: connection, + }, + }) + + // setup space + const space = await alice.createSpace('upload-test') + const auth = await space.createAuthorization(alice) + await alice.addSpace(auth) + await alice.setCurrentSpace(space.did()) + + // Then we setup a billing for this account + await provisionsStorage.put({ + // @ts-expect-error + provider: connection.id.did(), + account: alice.agent.did(), + consumer: space.did(), + }) - await assert.doesNotReject(() => alice.remove(contentCID)) + const root = await alice.uploadFile(new Blob([bytes])) - assert(service.upload.remove.called) - assert.equal(service.upload.remove.callCount, 1) - assert.equal(service.store.remove.callCount, 0) - }) + assert.deepEqual(await uploadTable.exists(space.did(), root), { + ok: true, + }) + + assert.deepEqual( + await alice + .remove(root) + .then((ok) => ({ ok: {} })) + .catch((error) => { + error + }), + { ok: {} } + ) + + assert.deepEqual(await uploadTable.exists(space.did(), root), { + ok: false, + }) + }, - it('should fail to remove uploaded shards if upload is not found', async () => { + 'should fail to remove uploaded shards if upload is not found': async ( + assert, + { connection } + ) => { const bytes = await randomBytes(128) const uploadedCar = await toCAR(bytes) const contentCID = uploadedCar.roots[0] - const service = mockService({ - upload: { - get: provide(UploadCapabilities.get, ({ invocation }) => { - return error(new StoreItemNotFound('did:web:any', uploadedCar.cid)) - }), - }, - }) - - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) - const alice = new Client(await AgentData.create(), { // @ts-ignore - serviceConf: await mockServiceConf(server), + serviceConf: { + access: connection, + upload: connection, + }, }) // setup space @@ -680,67 +488,20 @@ describe('Client', () => { await alice.setCurrentSpace(space.did()) await assert.rejects(alice.remove(contentCID, { shards: true })) + }, - assert(service.upload.get.called) - assert.equal(service.upload.get.callCount, 1) - assert.equal(service.store.remove.callCount, 0) - assert.equal(service.upload.remove.callCount, 0) - }) - - it('should not fail to remove if shard is not found', async () => { + 'should not fail to remove if shard is not found': async ( + assert, + { connection, provisionsStorage, uploadTable } + ) => { const bytesArray = [await randomBytes(128), await randomBytes(128)] - const uploadedCars = await Promise.all( - bytesArray.map((bytes) => toCAR(bytes)) - ) - const contentCID = uploadedCars[0].roots[0] - - const service = mockService({ - upload: { - get: provide(UploadCapabilities.get, ({ invocation }) => { - return { - ok: { - root: uploadedCars[0].roots[0], - shards: uploadedCars.map((car) => car.cid), - insertedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }, - } - }), - remove: provide(UploadCapabilities.remove, ({ invocation }) => { - return { - ok: { - root: uploadedCars[0].roots[0], - shards: uploadedCars.map((car) => car.cid), - }, - } - }), - }, - store: { - remove: provide( - StoreCapabilities.remove, - ({ invocation, capability }) => { - // Fail for first as not found) - if (capability.nb.link.equals(uploadedCars[0].cid)) { - return error( - new StoreItemNotFound('did:web:any', uploadedCars[0].cid) - ) - } - return { ok: { size: uploadedCars[1].size } } - } - ), - }, - }) - - const server = createServer({ - id: await Signer.generate(), - service, - codec: CAR.inbound, - validateAuthorization, - }) const alice = new Client(await AgentData.create(), { // @ts-ignore - serviceConf: await mockServiceConf(server), + serviceConf: { + access: connection, + upload: connection, + }, }) // setup space @@ -749,16 +510,36 @@ describe('Client', () => { await alice.addSpace(auth) await alice.setCurrentSpace(space.did()) - await assert.doesNotReject(() => - alice.remove(contentCID, { shards: true }) - ) + // Then we setup a billing for this account + await provisionsStorage.put({ + // @ts-expect-error + provider: connection.id.did(), + account: alice.agent.did(), + consumer: space.did(), + }) + + const root = await alice.uploadFile(new Blob(bytesArray)) + + const upload = await uploadTable.get(space.did(), root) - assert(service.upload.remove.called) - assert.equal(service.upload.remove.callCount, 1) - assert.equal(service.store.remove.callCount, 2) - }) + const shard = upload.ok?.shards?.[0] + if (!shard) { + return assert.ok(shard) + } - it('should not allow remove without a current space', async () => { + // delete shard + assert.ok((await alice.capability.store.remove(shard)).ok) + + assert.deepEqual( + await alice + .remove(root, { shards: true }) + .then(() => ({ ok: {} })) + .catch((error) => ({ error })), + { ok: {} } + ) + }, + + 'should not allow remove without a current space': async (assert) => { const alice = new Client(await AgentData.create()) const bytes = await randomBytes(128) @@ -766,6 +547,8 @@ describe('Client', () => { const contentCID = uploadedCar.roots[0] await assert.rejects(alice.remove(contentCID, { shards: true })) - }) - }) -}) + }, + }), +} + +Test.test({ Client: testClient }) diff --git a/packages/w3up-client/test/coupon.test.js b/packages/w3up-client/test/coupon.test.js index 5e5d1bea8..faee5f227 100644 --- a/packages/w3up-client/test/coupon.test.js +++ b/packages/w3up-client/test/coupon.test.js @@ -4,7 +4,7 @@ import * as Result from '../src/result.js' /** * @type {Test.Suite} */ -export const testCoupon = { +export const testCoupon = Test.withContext({ 'account.coupon': async ( assert, { client, mail, connect, grantAccess, plansStorage } @@ -85,6 +85,6 @@ export const testCoupon = { assert.match(fail.message, /Invalid CAR header format/) }, -} +}) Test.test({ Access: testCoupon }) diff --git a/packages/w3up-client/test/helpers/bucket-server.js b/packages/w3up-client/test/helpers/bucket-server.js index 43e8602bf..cd2540a57 100644 --- a/packages/w3up-client/test/helpers/bucket-server.js +++ b/packages/w3up-client/test/helpers/bucket-server.js @@ -1,6 +1,6 @@ import { createServer } from 'http' -const port = process.env.PORT ?? 9000 +const port = process.env.PORT ?? 8989 const status = process.env.STATUS ? parseInt(process.env.STATUS) : 200 const server = createServer((req, res) => { @@ -14,3 +14,5 @@ const server = createServer((req, res) => { // eslint-disable-next-line no-console server.listen(port, () => console.log(`Listening on :${port}`)) + +process.on('SIGTERM', () => process.exit(0)) diff --git a/packages/w3up-client/test/helpers/mocks.js b/packages/w3up-client/test/helpers/mocks.js deleted file mode 100644 index 88bf5bbe6..000000000 --- a/packages/w3up-client/test/helpers/mocks.js +++ /dev/null @@ -1,91 +0,0 @@ -import * as Server from '@ucanto/server' -import { connect } from '@ucanto/client' -import * as CAR from '@ucanto/transport/car' - -const notImplemented = () => { - throw new Server.Failure('not implemented') -} - -/** - * @param {Partial<{ - * access: Partial - * provider: Partial - * store: Partial - * subscription: Partial - * upload: Partial - * space: Partial - * ucan: Partial - * filecoin: Partial - * usage: Partial - * }>} impl - */ -export function mockService(impl) { - return { - store: { - add: withCallCount(impl.store?.add ?? notImplemented), - get: withCallCount(impl.store?.get ?? notImplemented), - list: withCallCount(impl.store?.list ?? notImplemented), - remove: withCallCount(impl.store?.remove ?? notImplemented), - }, - upload: { - add: withCallCount(impl.upload?.add ?? notImplemented), - get: withCallCount(impl.upload?.get ?? notImplemented), - list: withCallCount(impl.upload?.list ?? notImplemented), - remove: withCallCount(impl.upload?.remove ?? notImplemented), - }, - space: { - info: withCallCount(impl.space?.info ?? notImplemented), - }, - subscription: { - list: withCallCount(impl.subscription?.list ?? notImplemented), - }, - access: { - claim: withCallCount(impl.access?.claim ?? notImplemented), - authorize: withCallCount(impl.access?.authorize ?? notImplemented), - delegate: withCallCount(impl.access?.delegate ?? notImplemented), - }, - provider: { - add: withCallCount(impl.provider?.add ?? notImplemented), - }, - ucan: { - revoke: withCallCount(impl.ucan?.revoke ?? notImplemented), - }, - filecoin: { - offer: withCallCount(impl.filecoin?.offer ?? notImplemented), - info: withCallCount(impl.filecoin?.info ?? notImplemented), - }, - usage: { - report: withCallCount(impl.usage?.report ?? notImplemented), - }, - } -} - -/** - * @template {Function} T - * @param {T} fn - */ -function withCallCount(fn) { - /** @param {T extends (...args: infer A) => any ? A : never} args */ - const countedFn = (...args) => { - countedFn.called = true - countedFn.callCount++ - return fn(...args) - } - countedFn.called = false - countedFn.callCount = 0 - return countedFn -} - -/** - * @template {string} K - * @template {Record} Service - describes methods exposed via ucanto server - * @param {import('@ucanto/interface').ServerView} server - */ -export async function mockServiceConf(server) { - const connection = connect({ - id: server.id, - codec: CAR.outbound, - channel: server, - }) - return { access: connection, upload: connection, filecoin: connection } -} diff --git a/packages/w3up-client/test/helpers/receipts-server.js b/packages/w3up-client/test/helpers/receipts-server.js index 50608c6c7..29a414ac6 100644 --- a/packages/w3up-client/test/helpers/receipts-server.js +++ b/packages/w3up-client/test/helpers/receipts-server.js @@ -28,3 +28,4 @@ const server = createServer((req, res) => { }) server.listen(port, () => console.log(`Listening on :${port}`)) +process.on('SIGTERM', () => process.exit(0)) diff --git a/packages/w3up-client/test/index.browser.test.js b/packages/w3up-client/test/index.browser.test.js index fff724a41..e4b8c3ef1 100644 --- a/packages/w3up-client/test/index.browser.test.js +++ b/packages/w3up-client/test/index.browser.test.js @@ -1,12 +1,17 @@ -import assert from 'assert' +import * as Test from './test.js' import { RS256 } from '@ipld/dag-ucan/signature' import { create } from '../src/index.js' -describe('create', () => { - it('should create RSA key', async () => { +/** + * @type {Test.Suite} + */ +export const testRSAKey = { + 'should create RSA key': async (assert) => { const client = await create() const signer = client.agent.issuer assert.equal(signer.signatureAlgorithm, 'RS256') assert.equal(signer.signatureCode, RS256) - }) -}) + }, +} + +Test.test({ RSA: testRSAKey }) diff --git a/packages/w3up-client/test/index.node.test.js b/packages/w3up-client/test/index.node.test.js index f9f263c2b..a32372946 100644 --- a/packages/w3up-client/test/index.node.test.js +++ b/packages/w3up-client/test/index.node.test.js @@ -1,18 +1,21 @@ -import assert from 'assert' +import * as Test from './test.js' import { Signer } from '@ucanto/principal/ed25519' import { EdDSA } from '@ipld/dag-ucan/signature' import { StoreConf } from '@web3-storage/access/stores/store-conf' import { create } from '../src/index.node.js' -describe('create', () => { - it('should create Ed25519 key', async () => { +/** + * @type {Test.Suite} + */ +export const testEd25519Key = { + 'should create Ed25519 key': async (assert) => { const client = await create() const signer = client.agent.issuer assert.equal(signer.signatureAlgorithm, 'EdDSA') assert.equal(signer.signatureCode, EdDSA) - }) + }, - it('should load from existing store', async () => { + 'should load from existing store': async (assert) => { const store = new StoreConf({ profile: 'w3up-client-test' }) await store.reset() @@ -20,9 +23,9 @@ describe('create', () => { const client1 = await create({ store }) assert.equal(client0.agent.did(), client1.agent.did()) - }) + }, - it('should allow BYO principal', async () => { + 'should allow BYO principal': async (assert) => { const store = new StoreConf({ profile: 'w3up-client-test' }) await store.reset() @@ -30,9 +33,9 @@ describe('create', () => { const client = await create({ principal, store }) assert.equal(client.agent.did(), principal.did()) - }) + }, - it('should throw for mismatched BYO principal', async () => { + 'should throw for mismatched BYO principal': async (assert) => { const store = new StoreConf({ profile: 'w3up-client-test' }) await store.reset() @@ -43,5 +46,7 @@ describe('create', () => { await assert.rejects(create({ principal: principal1, store }), { message: `store cannot be used with ${principal1.did()}, stored principal and passed principal must match`, }) - }) -}) + }, +} + +Test.test({ Ed25519: testEd25519Key }) diff --git a/packages/w3up-client/test/result.test.js b/packages/w3up-client/test/result.test.js index c568945b8..8625c0176 100644 --- a/packages/w3up-client/test/result.test.js +++ b/packages/w3up-client/test/result.test.js @@ -1,12 +1,17 @@ import * as Result from '../src/result.js' -import assert from 'assert' +import * as Test from './test.js' -describe('Result', () => { - it('expect throws on error', async () => { +/** + * @type {Test.Suite} + */ +export const testResult = { + 'expect throws on error': async (assert) => { assert.throws(() => Result.try({ error: new Error('Boom') }), /Boom/) - }) + }, - it('expect returns ok value if not an error', () => { + 'expect returns ok value if not an error': (assert) => { assert.equal(Result.try({ ok: 'ok' }), 'ok') - }) -}) + }, +} + +Test.test({ Result: testResult }) diff --git a/packages/w3up-client/test/space.test.js b/packages/w3up-client/test/space.test.js index de9bf395e..a6a0d1281 100644 --- a/packages/w3up-client/test/space.test.js +++ b/packages/w3up-client/test/space.test.js @@ -9,7 +9,7 @@ import { randomCAR } from './helpers/random.js' /** * @type {Test.Suite} */ -export const testSpace = { +export const testSpace = Test.withContext({ 'should get meta': async (assert, { client }) => { const signer = await Signer.generate() const name = `space-${Date.now()}` @@ -51,6 +51,6 @@ export const testSpace = { const usage = Result.unwrap(await found.usage.get()) assert.equal(usage, BigInt(size)) }, -} +}) Test.test({ Space: testSpace }) diff --git a/packages/w3up-client/test/test.js b/packages/w3up-client/test/test.js index 7bf1f9105..fd0647944 100644 --- a/packages/w3up-client/test/test.js +++ b/packages/w3up-client/test/test.js @@ -3,10 +3,56 @@ import * as Context from '@web3-storage/upload-api/test/context' import * as Client from '@web3-storage/w3up-client' import * as assert from 'assert' +/** + * @template [Context=void] + * @typedef {(assert: Assert, context: Context) => unknown} Unit + */ +/** + * @template [Context=undefined] + * @typedef {object} Setup + * @property {() => Context|PromiseLike} [before] + * @property {() => PromiseLike|unknown} [after] + */ + +/** + * @template [Context=undefined] + * @typedef {{[name:string]: Unit|Suite}} Suite + */ + +/** + * @template Context + * @param {object} descriptor + * @param {(assert: Assert) => PromiseLike} descriptor.before + * @param {(context: Context) => unknown} descriptor.after + * @returns {(suite: Suite) => Suite} + */ + +export const group = + ({ before, after }) => + (suite) => { + return Object.fromEntries( + Object.entries(suite).map(([key, test]) => [ + key, + typeof test === 'function' + ? async (assert) => { + const context = await before(assert) + try { + await test(assert, context) + } finally { + await after(context) + } + } + : group({ before, after })(test), + ]) + ) + } + /** * @typedef {Omit & {ok(value:unknown, message?:string):void}} Assert - * @typedef {Record>) => unknown>} Suite - * @param {Suite|Record} suite + */ + +/** + * @param {Suite|Record} suite */ export const test = (suite) => { for (const [name, member] of Object.entries(suite)) { @@ -17,14 +63,7 @@ export const test = (suite) => { ? it.skip : it - define(name, async () => { - const context = await setup() - try { - await member(assert, context) - } finally { - await Context.cleanupContext(context) - } - }) + define(name, () => member(assert)) } else { describe(name, () => test(member)) } @@ -46,5 +85,15 @@ export const setup = async () => { }, }) - return { ...context, connect, client: await connect() } + return { + ...context, + connect, + client: await connect(), + cleanup: () => Context.cleanupContext(context), + } } + +export const withContext = group({ + before: setup, + after: (context) => Context.cleanupContext(context), +})