From 9984a071f4e34505f668afc1708acf4bc01a32a0 Mon Sep 17 00:00:00 2001 From: Felipe Forbeck Date: Fri, 29 Nov 2024 13:36:29 -0300 Subject: [PATCH] fix and add tests --- packages/filecoin-api/test/context/service.js | 11 - packages/w3up-client/src/client.js | 12 +- packages/w3up-client/test/account.test.js | 22 +- .../test/capability/filecoin.test.js | 8 +- .../w3up-client/test/capability/index.test.js | 4 +- .../test/capability/subscription.test.js | 4 +- .../test/capability/upload.test.js | 16 +- packages/w3up-client/test/client.test.js | 301 ++++++++++++++---- packages/w3up-client/test/coupon.test.js | 4 +- packages/w3up-client/test/mocks/service.js | 17 +- packages/w3up-client/test/space.test.js | 4 +- 11 files changed, 306 insertions(+), 97 deletions(-) diff --git a/packages/filecoin-api/test/context/service.js b/packages/filecoin-api/test/context/service.js index 44b0635dd..37de7be45 100644 --- a/packages/filecoin-api/test/context/service.js +++ b/packages/filecoin-api/test/context/service.js @@ -13,7 +13,6 @@ import * as API from '../../src/types.js' import { validateAuthorization } from '../utils.js' import { mockService } from './mocks.js' -import { Access } from '@web3-storage/capabilities' export { getStoreImplementations } from './store-implementations.js' export { getQueueImplementations } from './queue-implementations.js' @@ -241,16 +240,6 @@ export function getMockService() { } ), }, - access: { - delegate: Server.provide( - Access.delegate, - async ({ capability, invocation }) => { - return { - ok: {}, - } - } - ), - }, }) } diff --git a/packages/w3up-client/src/client.js b/packages/w3up-client/src/client.js index e5e0e43b5..982a4ece2 100644 --- a/packages/w3up-client/src/client.js +++ b/packages/w3up-client/src/client.js @@ -30,7 +30,6 @@ import { FilecoinClient } from './capability/filecoin.js' import { CouponAPI } from './coupon.js' export * as Access from './capability/access.js' import * as Result from './result.js' -import { remove } from '@web3-storage/capabilities/store' export { AccessClient, @@ -260,7 +259,7 @@ export class Client extends Base { * @typedef {object} SpaceCreateOptions * @property {boolean} [skipContentServeAuthorization] - Whether to skip the Content Serve authorization. It means that the content of the space will not be served by any Content Serve Service. * @property {`did:${string}:${string}`[]} [authorizeContentServeServices] - The DID Key or DID Web of the Content Serve Service to authorize to serve content from the created space. - * @property {import('./types.js').ConnectionView} [authorizeContentServeServices.connection] - The connection to the Content Serve Service that will handle, validate, and store the access/delegate UCAN invocation. + * @property {import('./types.js').ConnectionView} [connection] - The connection to the Content Serve Service that will handle, validate, and store the access/delegate UCAN invocation. * @property {Account.Account} [account] - The account configured as the recovery account for the space. * @property {string} [name] - The name of the space to create. * @@ -269,10 +268,8 @@ export class Client extends Base { * @returns {Promise} The created space owned by the agent. */ async createSpace(name, options) { - const space = await this._agent.createSpace(name) - // Save the space to authorize the client to use the space - await space.save() + const space = await this._agent.createSpace(name) const account = options.account if (account) { @@ -285,6 +282,9 @@ export class Client extends Base { ) } + // Save the space to authorize the client to use the space + await space.save() + // Create a recovery for the account const recovery = await space.createRecovery(account.did()) @@ -380,6 +380,7 @@ export class Client extends Base { }) .execute(options.connection) + /* c8 ignore next 8 - can't mock this error */ if (verificationResult.out.error) { throw new Error( `failed to publish delegation for audience ${options.audience}: ${verificationResult.out.error.message}`, @@ -388,7 +389,6 @@ export class Client extends Base { } ) } - return { ok: { ...verificationResult.out.ok, delegation } } } finally { if (currentSpace) { diff --git a/packages/w3up-client/test/account.test.js b/packages/w3up-client/test/account.test.js index 573907b6c..0278c527b 100644 --- a/packages/w3up-client/test/account.test.js +++ b/packages/w3up-client/test/account.test.js @@ -111,7 +111,9 @@ export const testAccount = Test.withContext({ assert, { client, mail, grantAccess } ) => { - const space = await client.createSpace('test') + const space = await client.createSpace('test', { + skipContentServeAuthorization: true, + }) const mnemonic = space.toMnemonic() const { signer } = await Space.fromMnemonic(mnemonic, { name: 'import' }) assert.deepEqual( @@ -147,7 +149,9 @@ export const testAccount = Test.withContext({ 'multi device workflow': async (asserts, { connect, mail, grantAccess }) => { const laptop = await connect() - const space = await laptop.createSpace('main') + const space = await laptop.createSpace('main', { + skipContentServeAuthorization: true, + }) // want to provision space ? const email = 'alice@web.mail' @@ -183,7 +187,9 @@ export const testAccount = Test.withContext({ asserts.deepEqual(result.did, space.did()) }, 'setup recovery': async (assert, { client, mail, grantAccess }) => { - const space = await client.createSpace('test') + const space = await client.createSpace('test', { + skipContentServeAuthorization: true, + }) const email = 'alice@web.mail' const login = Account.login(client, email) @@ -280,7 +286,9 @@ export const testAccount = Test.withContext({ assert, { client, mail, grantAccess } ) => { - const space = await client.createSpace('test') + const space = await client.createSpace('test', { + skipContentServeAuthorization: true, + }) const email = 'alice@web.mail' const login = Account.login(client, email) @@ -299,8 +307,10 @@ export const testAccount = Test.withContext({ assert.equal(typeof subs.results[0].subscription, 'string') }, - 'space.save': async (assert, { client, mail, grantAccess }) => { - const space = await client.createSpace('test') + 'space.save': async (assert, { client }) => { + const space = await client.createSpace('test', { + skipContentServeAuthorization: true, + }) assert.deepEqual(client.spaces(), []) const result = await space.save() diff --git a/packages/w3up-client/test/capability/filecoin.test.js b/packages/w3up-client/test/capability/filecoin.test.js index f2640bece..c8795171a 100644 --- a/packages/w3up-client/test/capability/filecoin.test.js +++ b/packages/w3up-client/test/capability/filecoin.test.js @@ -5,7 +5,9 @@ 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 space = await alice.createSpace('test', { + skipContentServeAuthorization: true, + }) const auth = await space.createAuthorization(alice) await alice.addSpace(auth) await alice.setCurrentSpace(space.did()) @@ -36,7 +38,9 @@ export const FilecoinClient = Test.withContext({ throw new Error('could not compute proof') } - const space = await alice.createSpace('test') + const space = await alice.createSpace('test', { + skipContentServeAuthorization: true, + }) const auth = await space.createAuthorization(alice) await alice.addSpace(auth) await alice.setCurrentSpace(space.did()) diff --git a/packages/w3up-client/test/capability/index.test.js b/packages/w3up-client/test/capability/index.test.js index 40196272c..3dbd4f4bf 100644 --- a/packages/w3up-client/test/capability/index.test.js +++ b/packages/w3up-client/test/capability/index.test.js @@ -14,7 +14,9 @@ export const IndexClient = Test.withContext({ ) => { const car = await randomCAR(128) - const space = await alice.createSpace('test') + const space = await alice.createSpace('test', { + skipContentServeAuthorization: true, + }) const auth = await space.createAuthorization(alice) await alice.addSpace(auth) await alice.setCurrentSpace(space.did()) diff --git a/packages/w3up-client/test/capability/subscription.test.js b/packages/w3up-client/test/capability/subscription.test.js index c4d036712..67fa48277 100644 --- a/packages/w3up-client/test/capability/subscription.test.js +++ b/packages/w3up-client/test/capability/subscription.test.js @@ -8,7 +8,9 @@ export const SubscriptionClient = Test.withContext({ assert, { client, connection, service, plansStorage, grantAccess, mail } ) => { - const space = await client.createSpace('test') + const space = await client.createSpace('test', { + skipContentServeAuthorization: true, + }) const email = 'alice@web.mail' const login = Account.login(client, email) const message = await mail.take() diff --git a/packages/w3up-client/test/capability/upload.test.js b/packages/w3up-client/test/capability/upload.test.js index 573221908..efbf5252e 100644 --- a/packages/w3up-client/test/capability/upload.test.js +++ b/packages/w3up-client/test/capability/upload.test.js @@ -9,7 +9,9 @@ export const UploadClient = Test.withContext({ ) => { const car = await randomCAR(128) - const space = await alice.createSpace('test') + const space = await alice.createSpace('test', { + skipContentServeAuthorization: true, + }) const auth = await space.createAuthorization(alice) await alice.addSpace(auth) await alice.setCurrentSpace(space.did()) @@ -38,7 +40,9 @@ export const UploadClient = Test.withContext({ ) => { const car = await randomCAR(128) - const space = await alice.createSpace('test') + const space = await alice.createSpace('test', { + skipContentServeAuthorization: true, + }) const auth = await space.createAuthorization(alice) await alice.addSpace(auth) await alice.setCurrentSpace(space.did()) @@ -77,7 +81,9 @@ export const UploadClient = Test.withContext({ ) => { const car = await randomCAR(128) - const space = await alice.createSpace('test') + const space = await alice.createSpace('test', { + skipContentServeAuthorization: true, + }) const auth = await space.createAuthorization(alice) await alice.addSpace(auth) await alice.setCurrentSpace(space.did()) @@ -109,7 +115,9 @@ export const UploadClient = Test.withContext({ ) => { const car = await randomCAR(128) - const space = await alice.createSpace('test') + const space = await alice.createSpace('test', { + skipContentServeAuthorization: true, + }) const auth = await space.createAuthorization(alice) await alice.addSpace(auth) await alice.setCurrentSpace(space.did()) diff --git a/packages/w3up-client/test/client.test.js b/packages/w3up-client/test/client.test.js index 56c184080..42cee03e4 100644 --- a/packages/w3up-client/test/client.test.js +++ b/packages/w3up-client/test/client.test.js @@ -1,5 +1,6 @@ import assert from 'assert' import { parseLink } from '@ucanto/server' +import * as Server from '@ucanto/server' import { Agent, AgentData, @@ -18,10 +19,9 @@ import { alice, confirmConfirmationUrl, gateway, - gatewaySigner, } from '../../upload-api/test/helpers/utils.js' import * as SpaceCapability from '@web3-storage/capabilities/space' -import { getConnection, getMockService } from './mocks/service.js' +import { getConnection, getContentServeMockService } from './mocks/service.js' /** @type {Test.Suite} */ export const testClient = { @@ -303,6 +303,7 @@ export const testClient = { // Step 2: Alice creates a space with her account as the recovery account const space = await client.createSpace('recovery-space-test', { account: aliceAccount, // The account is the recovery account + skipContentServeAuthorization: true, }) assert.ok(space) @@ -334,7 +335,9 @@ export const testClient = { await aliceLogin // Step 2: Alice creates a space without providing a recovery account - const space = await client.createSpace('no-recovery-space-test') + const space = await client.createSpace('no-recovery-space-test', { + skipContentServeAuthorization: true, + }) assert.ok(space) // Step 3: Attempt to access the space from a new device @@ -437,6 +440,7 @@ export const testClient = { // Step 2: Alice creates a space const space = await aliceClient.createSpace('share-space-test', { account: aliceAccount, + skipContentServeAuthorization: true, }) assert.ok(space) @@ -489,6 +493,7 @@ export const testClient = { 'share-space-delegate-fail-test', { account: aliceAccount, + skipContentServeAuthorization: true, } ) assert.ok(space) @@ -525,12 +530,14 @@ export const testClient = { // Step 2: Alice creates a space const spaceA = await client.createSpace('test-space-a', { account: aliceAccount, + skipContentServeAuthorization: true, }) assert.ok(spaceA) // Step 3: Alice creates another space to share with a friend const spaceB = await client.createSpace('test-space-b', { account: aliceAccount, + skipContentServeAuthorization: true, }) assert.ok(spaceB) @@ -548,62 +555,248 @@ export const testClient = { }, }), authorizeGateway: Test.withContext({ - 'should authorize a gateway to serve content from a space': async ( - assert, - { mail, grantAccess, connection } - ) => { - // Step 1: Create a client for Alice and login - const aliceClient = new Client( - await AgentData.create({ - principal: alice, - }), - { - // @ts-ignore - serviceConf: { - access: connection, - upload: connection, - }, + 'should explicitly authorize a gateway to serve content from a space': + async (assert, { mail, grantAccess, connection }) => { + // Step 1: Create a client for Alice and login + const aliceClient = new Client( + await AgentData.create({ + principal: alice, + }), + { + // @ts-ignore + serviceConf: { + access: connection, + upload: connection, + }, + } + ) + + const aliceEmail = 'alice@web.mail' + const aliceLogin = aliceClient.login(aliceEmail) + const message = await mail.take() + assert.deepEqual(message.to, aliceEmail) + await grantAccess(message) + const aliceAccount = await aliceLogin + + // Step 2: Alice creates a space + const spaceA = await aliceClient.createSpace( + 'authorize-gateway-space', + { + account: aliceAccount, + skipContentServeAuthorization: true, + } + ) + assert.ok(spaceA) + + const gatewayService = getContentServeMockService() + const gatewayConnection = getConnection( + gateway, + gatewayService + ).connection + + // Step 3: Alice authorizes the gateway to serve content from the space + const delegationResult = await aliceClient.authorizeContentServe( + spaceA, + { + audience: gateway.did(), + connection: gatewayConnection, + } + ) + assert.ok(delegationResult.ok) + const { delegation } = delegationResult.ok + + // Step 4: Find the delegation for the default gateway + assert.equal(delegation.audience.did(), gateway.did()) + assert.ok( + delegation.capabilities.some( + (c) => + c.can === SpaceCapability.contentServe.can && + c.with === spaceA.did() + ) + ) + }, + 'should automatically authorize a gateway to serve content from a space when the space is created': + async (assert, { mail, grantAccess, connection }) => { + // Step 1: Create a client for Alice and login + const aliceClient = new Client( + await AgentData.create({ + principal: alice, + }), + { + // @ts-ignore + serviceConf: { + access: connection, + upload: connection, + }, + } + ) + + const aliceEmail = 'alice@web.mail' + const aliceLogin = aliceClient.login(aliceEmail) + const message = await mail.take() + assert.deepEqual(message.to, aliceEmail) + await grantAccess(message) + const aliceAccount = await aliceLogin + + // Step 2: Alice creates a space + const gatewayService = getContentServeMockService() + const gatewayConnection = getConnection( + gateway, + gatewayService + ).connection + + try { + const spaceA = await aliceClient.createSpace( + 'authorize-gateway-space', + { + account: aliceAccount, + authorizeContentServeServices: [gateway.did()], + connection: gatewayConnection, + } + ) + assert.ok(spaceA, 'should create the space') + } catch (error) { + assert.fail(error, 'should not throw when creating the space') } - ) + }, + 'should throw when the content serve authorization fails due to missing connection configuration': + async (assert, { mail, grantAccess, connection }) => { + // Step 1: Create a client for Alice and login + const aliceClient = new Client( + await AgentData.create({ + principal: alice, + }), + { + // @ts-ignore + serviceConf: { + access: connection, + upload: connection, + }, + } + ) - const aliceEmail = 'alice@web.mail' - const aliceLogin = aliceClient.login(aliceEmail) - const message = await mail.take() - assert.deepEqual(message.to, aliceEmail) - await grantAccess(message) - const aliceAccount = await aliceLogin + const aliceEmail = 'alice@web.mail' + const aliceLogin = aliceClient.login(aliceEmail) + const message = await mail.take() + assert.deepEqual(message.to, aliceEmail) + await grantAccess(message) + const aliceAccount = await aliceLogin - // Step 2: Alice creates a space - const spaceA = await aliceClient.createSpace('authorize-gateway-space', { - account: aliceAccount, - skipContentServeAuthorization: true, - }) - assert.ok(spaceA) + try { + const spaceA = await aliceClient.createSpace( + 'authorize-gateway-space', + { + account: aliceAccount, + authorizeContentServeServices: [gateway.did()], + } + ) + assert.fail(spaceA, 'should not create the space') + } catch (error) { + assert.match( + // @ts-expect-error + error.message, + /missing option/, + 'should throw when creating the space' + ) + } + }, + 'should throw when the content serve authorization fails due to missing service configuration': + async (assert, { mail, grantAccess, connection }) => { + // Step 1: Create a client for Alice and login + const aliceClient = new Client( + await AgentData.create({ + principal: alice, + }), + { + // @ts-ignore + serviceConf: { + access: connection, + upload: connection, + }, + } + ) - const gatewayService = getMockService() - const gatewayConnection = getConnection( - gateway, - gatewayService - ).connection - - // Step 3: Alice authorizes the gateway to serve content from the space - const delegationResult = await aliceClient.authorizeContentServe(spaceA, { - audience: gateway.did(), - connection: gatewayConnection, - }) - assert.ok(delegationResult.ok) - const { delegation } = delegationResult.ok - - // Step 4: Find the delegation for the default gateway - assert.equal(delegation.audience.did(), gateway.did()) - assert.ok( - delegation.capabilities.some( - (c) => - c.can === SpaceCapability.contentServe.can && - c.with === spaceA.did() + const aliceEmail = 'alice@web.mail' + const aliceLogin = aliceClient.login(aliceEmail) + const message = await mail.take() + assert.deepEqual(message.to, aliceEmail) + await grantAccess(message) + const aliceAccount = await aliceLogin + + const gatewayService = getContentServeMockService() + const gatewayConnection = getConnection( + gateway, + gatewayService + ).connection + try { + const spaceA = await aliceClient.createSpace( + 'authorize-gateway-space', + { + account: aliceAccount, + authorizeContentServeServices: [], // No services to authorize + connection: gatewayConnection, + } + ) + assert.fail(spaceA, 'should not create the space') + } catch (error) { + assert.match( + // @ts-expect-error + error.message, + /missing option/, + 'should throw when creating the space' + ) + } + }, + 'should throw when content serve service can not process the invocation': + async (assert, { mail, grantAccess, connection }) => { + // Step 1: Create a client for Alice and login + const aliceClient = new Client( + await AgentData.create({ + principal: alice, + }), + { + // @ts-ignore + serviceConf: { + access: connection, + upload: connection, + }, + } ) - ) - }, + + const aliceEmail = 'alice@web.mail' + const aliceLogin = aliceClient.login(aliceEmail) + const message = await mail.take() + assert.deepEqual(message.to, aliceEmail) + await grantAccess(message) + const aliceAccount = await aliceLogin + + // Step 2: Alice creates a space + const gatewayService = getContentServeMockService({ + error: Server.fail( + 'Content serve service can not process the invocation' + ).error, + }) + const gatewayConnection = getConnection( + gateway, + gatewayService + ).connection + + try { + await aliceClient.createSpace('authorize-gateway-space', { + account: aliceAccount, + authorizeContentServeServices: [gateway.did()], + connection: gatewayConnection, + }) + assert.fail('should not create the space') + } catch (error) { + assert.match( + // @ts-expect-error + error.message, + /failed to publish delegation for audience/, + 'should throw when publishing the delegation' + ) + } + }, }), proofs: { 'should get proofs': async (assert) => { diff --git a/packages/w3up-client/test/coupon.test.js b/packages/w3up-client/test/coupon.test.js index faee5f227..c44aa7fce 100644 --- a/packages/w3up-client/test/coupon.test.js +++ b/packages/w3up-client/test/coupon.test.js @@ -37,7 +37,9 @@ export const testCoupon = Test.withContext({ const access = await alice.coupon.redeem(archive) // creates a space and provision it with redeemed coupon - const space = await alice.createSpace('home') + const space = await alice.createSpace('home', { + skipContentServeAuthorization: true, + }) const result = await space.provision(access) await space.save() diff --git a/packages/w3up-client/test/mocks/service.js b/packages/w3up-client/test/mocks/service.js index 761e0666e..3e86a3266 100644 --- a/packages/w3up-client/test/mocks/service.js +++ b/packages/w3up-client/test/mocks/service.js @@ -1,28 +1,25 @@ import * as Client from '@ucanto/client' import * as Server from '@ucanto/server' import * as CAR from '@ucanto/transport/car' - import * as AccessCaps from '@web3-storage/capabilities' /** * Mocked Gateway/Content Serve service + * @param {{ ok: any } | { error: Server.API.Failure }} result */ -export function getMockService() { +export function getContentServeMockService(result = { ok: {} }) { return { access: { - delegate: Server.provide( - AccessCaps.Access.delegate, - async ({ capability, invocation }) => { - return { - ok: {}, - } - } - ), + delegate: Server.provide(AccessCaps.Access.delegate, async () => { + return result + }), }, } } /** + * Generic function to create connection to any type of mock service with any type of signer id. + * * @param {any} service * @param {any} id */ diff --git a/packages/w3up-client/test/space.test.js b/packages/w3up-client/test/space.test.js index a6a0d1281..838b534ec 100644 --- a/packages/w3up-client/test/space.test.js +++ b/packages/w3up-client/test/space.test.js @@ -24,7 +24,9 @@ export const testSpace = Test.withContext({ }, 'should get usage': async (assert, { client, grantAccess, mail }) => { - const space = await client.createSpace('test') + const space = await client.createSpace('test', { + skipContentServeAuthorization: true, + }) const email = 'alice@web.mail' const login = Account.login(client, email)