From 0a921342206ee6a990dc85160d58b6f7e25a6502 Mon Sep 17 00:00:00 2001 From: Joao Andrade Date: Mon, 6 May 2024 23:34:50 +0100 Subject: [PATCH] fix: test blob upload on w3up-client tests --- packages/access-client/src/types.ts | 4 + packages/upload-client/src/blob.js | 4 +- packages/upload-client/test/helpers/utils.js | 3 +- packages/w3up-client/src/service.js | 9 +- packages/w3up-client/src/types.ts | 5 +- packages/w3up-client/test/client.test.js | 22 ++-- packages/w3up-client/test/helpers/mocks.js | 5 + packages/w3up-client/test/helpers/utils.js | 118 +++++++++++++++++++ packages/w3up-client/test/test.js | 1 + 9 files changed, 154 insertions(+), 17 deletions(-) diff --git a/packages/access-client/src/types.ts b/packages/access-client/src/types.ts index 4557554f5..7b0e5d6e0 100644 --- a/packages/access-client/src/types.ts +++ b/packages/access-client/src/types.ts @@ -46,6 +46,9 @@ import type { UCANRevoke, UCANRevokeSuccess, UCANRevokeFailure, + UCANConclude, + UCANConcludeSuccess, + UCANConcludeFailure, AccountDID, ProviderDID, SpaceDID, @@ -132,6 +135,7 @@ export interface Service { } ucan: { revoke: ServiceMethod + conclude: ServiceMethod, } plan: { get: ServiceMethod diff --git a/packages/upload-client/src/blob.js b/packages/upload-client/src/blob.js index 5eee5cb48..863c2f965 100644 --- a/packages/upload-client/src/blob.js +++ b/packages/upload-client/src/blob.js @@ -31,7 +31,7 @@ function createUploadProgressHandler(url, handler) { /** * @param {import('@ucanto/interface').Invocation} concludeFx */ -export function getConcludeReceipt(concludeFx) { +function getConcludeReceipt(concludeFx) { const receiptBlocks = new Map() for (const block of concludeFx.iterateIPLDBlocks()) { receiptBlocks.set(`${block.cid}`, block) @@ -47,7 +47,7 @@ export function getConcludeReceipt(concludeFx) { /** * @param {import('@ucanto/interface').Receipt} receipt */ -export function parseBlobAddReceiptNext(receipt) { +function parseBlobAddReceiptNext(receipt) { // Get invocations next /** * @type {import('@ucanto/interface').Invocation[]} diff --git a/packages/upload-client/test/helpers/utils.js b/packages/upload-client/test/helpers/utils.js index 11df57e33..53bef2123 100644 --- a/packages/upload-client/test/helpers/utils.js +++ b/packages/upload-client/test/helpers/utils.js @@ -1,9 +1,10 @@ import { Receipt } from '@ucanto/core' +import { conclude } from '@web3-storage/capabilities/ucan' import * as Server from '@ucanto/server' import * as HTTP from '@web3-storage/capabilities/http' import * as W3sBlobCapabilities from '@web3-storage/capabilities/web3.storage/blob' import { W3sBlob } from '@web3-storage/capabilities' -import { createConcludeInvocation } from '../../src/blob.js' +import { createConcludeInvocation } from '../../../upload-client/src/blob.js' export const validateAuthorization = () => ({ ok: {} }) diff --git a/packages/w3up-client/src/service.js b/packages/w3up-client/src/service.js index 896579da9..c1838b7c1 100644 --- a/packages/w3up-client/src/service.js +++ b/packages/w3up-client/src/service.js @@ -1,11 +1,11 @@ -import { connect } from '@ucanto/client' +import * as client from '@ucanto/client' import { CAR, HTTP } from '@ucanto/transport' import * as DID from '@ipld/dag-ucan/did' export const accessServiceURL = new URL('https://up.web3.storage') export const accessServicePrincipal = DID.parse('did:web:web3.storage') -export const accessServiceConnection = connect({ +export const accessServiceConnection = client.connect({ id: accessServicePrincipal, codec: CAR.outbound, channel: HTTP.open({ @@ -17,7 +17,7 @@ export const accessServiceConnection = connect({ export const uploadServiceURL = new URL('https://up.web3.storage') export const uploadServicePrincipal = DID.parse('did:web:web3.storage') -export const uploadServiceConnection = connect({ +export const uploadServiceConnection = client.connect({ id: uploadServicePrincipal, codec: CAR.outbound, channel: HTTP.open({ @@ -29,7 +29,7 @@ export const uploadServiceConnection = connect({ export const filecoinServiceURL = new URL('https://up.web3.storage') export const filecoinServicePrincipal = DID.parse('did:web:web3.storage') -export const filecoinServiceConnection = connect({ +export const filecoinServiceConnection = client.connect({ id: filecoinServicePrincipal, codec: CAR.outbound, channel: HTTP.open({ @@ -40,6 +40,7 @@ export const filecoinServiceConnection = connect({ /** @type {import('./types.js').ServiceConf} */ export const serviceConf = { + blob: uploadServiceConnection, access: accessServiceConnection, upload: uploadServiceConnection, filecoin: filecoinServiceConnection, diff --git a/packages/w3up-client/src/types.ts b/packages/w3up-client/src/types.ts index 3f5d2abce..9dfe37873 100644 --- a/packages/w3up-client/src/types.ts +++ b/packages/w3up-client/src/types.ts @@ -3,7 +3,9 @@ import { type Service as AccessService, type AgentDataExport, } from '@web3-storage/access/types' -import { type Service as UploadService } from '@web3-storage/upload-client/types' +import { + type Service as UploadService, +} from '@web3-storage/upload-client/types' import type { ConnectionView, Signer, @@ -31,6 +33,7 @@ export type ProofQuery = Record> export type Service = AccessService & UploadService & StorefrontService export interface ServiceConf { + blob: ConnectionView access: ConnectionView upload: ConnectionView filecoin: ConnectionView diff --git a/packages/w3up-client/test/client.test.js b/packages/w3up-client/test/client.test.js index be37a3fb6..90edb8b02 100644 --- a/packages/w3up-client/test/client.test.js +++ b/packages/w3up-client/test/client.test.js @@ -9,6 +9,7 @@ import { } from '@ucanto/server' import * as CAR from '@ucanto/transport/car' import * as Signer from '@ucanto/principal/ed25519' +import * as BlobCapabilities from '@web3-storage/capabilities/store' import * as StoreCapabilities from '@web3-storage/capabilities/store' import * as UploadCapabilities from '@web3-storage/capabilities/upload' import * as UCANCapabilities from '@web3-storage/capabilities/ucan' @@ -35,11 +36,12 @@ describe('Client', () => { let carCID const service = mockService({ - store: { - add: provide(StoreCapabilities.add, ({ invocation, capability }) => { + blob: { + // @ts-ignore Argument of type + add: provide(BlobCapabilities.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.can, BlobCapabilities.add.can) assert.equal(capability.with, alice.currentSpace()?.did()) return { @@ -144,11 +146,12 @@ describe('Client', () => { let carCID const service = mockService({ - store: { - add: provide(StoreCapabilities.add, ({ invocation, capability }) => { + blob: { + // @ts-ignore Argument of type + add: provide(BlobCapabilities.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.can, BlobCapabilities.add.can) assert.equal(capability.with, alice.currentSpace()?.did()) return { ok: { @@ -236,11 +239,12 @@ describe('Client', () => { let carCID const service = mockService({ - store: { - add: provide(StoreCapabilities.add, ({ invocation, capability }) => { + blob: { + // @ts-ignore Argument of type + add: provide(BlobCapabilities.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.can, BlobCapabilities.add.can) assert.equal(capability.with, space.did()) return { ok: { diff --git a/packages/w3up-client/test/helpers/mocks.js b/packages/w3up-client/test/helpers/mocks.js index 88bf5bbe6..9725d5793 100644 --- a/packages/w3up-client/test/helpers/mocks.js +++ b/packages/w3up-client/test/helpers/mocks.js @@ -8,6 +8,7 @@ const notImplemented = () => { /** * @param {Partial<{ + * blob: Partial * access: Partial * provider: Partial * store: Partial @@ -21,6 +22,9 @@ const notImplemented = () => { */ export function mockService(impl) { return { + blob: { + add: withCallCount(impl.blob?.add ?? notImplemented), + }, store: { add: withCallCount(impl.store?.add ?? notImplemented), get: withCallCount(impl.store?.get ?? notImplemented), @@ -48,6 +52,7 @@ export function mockService(impl) { add: withCallCount(impl.provider?.add ?? notImplemented), }, ucan: { + conclude: withCallCount(impl.ucan?.conclude ?? notImplemented), revoke: withCallCount(impl.ucan?.revoke ?? notImplemented), }, filecoin: { diff --git a/packages/w3up-client/test/helpers/utils.js b/packages/w3up-client/test/helpers/utils.js index 4dd6dbc66..8494b55c3 100644 --- a/packages/w3up-client/test/helpers/utils.js +++ b/packages/w3up-client/test/helpers/utils.js @@ -1,5 +1,9 @@ +import { Receipt } from '@ucanto/core' import * as Server from '@ucanto/server' import { UCAN } from '@web3-storage/capabilities' +import * as HTTP from '@web3-storage/capabilities/http' +import * as W3sBlobCapabilities from '@web3-storage/capabilities/web3.storage/blob' +import { W3sBlob } from '@web3-storage/capabilities' import * as Types from '../../src/types.js' export const validateAuthorization = () => ({ ok: {} }) @@ -42,3 +46,117 @@ export const createAuthorization = async ({ account, agent, service }) => { return [authorization, attest] } + +// FIXME this code has been copied over from upload-api +/** + * @param {import('@ucanto/interface').Signer} id + * @param {import('@ucanto/interface').Verifier} serviceDid + * @param {import('@ucanto/interface').Receipt} receipt + */ +function createConcludeInvocation(id, serviceDid, receipt) { + const receiptBlocks = [] + const receiptCids = [] + for (const block of receipt.iterateIPLDBlocks()) { + receiptBlocks.push(block) + receiptCids.push(block.cid) + } + const concludeAllocatefx = conclude.invoke({ + issuer: id, + audience: serviceDid, + with: id.toDIDKey(), + nb: { + receipt: receipt.link(), + }, + expiration: Infinity, + facts: [ + { + ...receiptCids, + }, + ], + }) + for (const block of receiptBlocks) { + concludeAllocatefx.attach(block) + } + + return concludeAllocatefx +} + +// @ts-ignore +export const setupBlobAddResponse = async function({ issuer, with: space, proofs, audience }, invocation) { + const blob = invocation.capabilities[0].nb.blob + const blobAllocateTask = await W3sBlob.allocate + .invoke({ + issuer, + audience, + with: space.did(), + nb: { + blob, + cause: invocation.link(), + space: space.did(), + }, + expiration: Infinity, + }) + .delegate() + const blobAllocateReceipt = await Receipt.issue({ + issuer, + ran: blobAllocateTask.cid, + result: { ok: {} }, + }) + const blobConcludeAllocate = await createConcludeInvocation(issuer, audience, blobAllocateReceipt).delegate() + + const blobPutTask = await HTTP.put + .invoke({ + issuer, + audience, + with: space.toDIDKey(), + nb: { + body: blob, + url: { + 'ucan/await': ['.out.ok.address.url', blobAllocateTask.link()], + }, + headers: { + 'ucan/await': ['.out.ok.address.headers', blobAllocateTask.link()], + }, + }, + facts: [ + { + keys: audience.toArchive(), + }, + ], + expiration: Infinity, + }) + .delegate() + + const blobAcceptTask = await W3sBlobCapabilities.accept + .invoke({ + issuer, + audience, + with: space.did(), + nb: { + blob, + space: space.did(), + _put: { 'ucan/await': ['.out.ok', blobPutTask.link()] }, + }, + proofs, + }) + .delegate() + + const blobAcceptReceipt = await Receipt.issue({ + issuer, + ran: blobAcceptTask.cid, + result: { ok: {} }, + }) + const blobConcludeAccept = await createConcludeInvocation(issuer, audience, blobAcceptReceipt).delegate() + + return Server + .ok({ + site: { + 'ucan/await': ['.out.ok.site', blobAcceptTask.link()], + }, + }) + .fork(blobAllocateTask) + .fork(blobConcludeAllocate) + .fork(blobPutTask) + .join(blobAcceptTask) + .fork(blobConcludeAccept) +} diff --git a/packages/w3up-client/test/test.js b/packages/w3up-client/test/test.js index 7bf1f9105..eb202f7c7 100644 --- a/packages/w3up-client/test/test.js +++ b/packages/w3up-client/test/test.js @@ -40,6 +40,7 @@ export const setup = async () => { Client.create({ store: new StoreMemory(), serviceConf: { + blob: context.connection, access: context.connection, upload: context.connection, filecoin: context.connection,