Skip to content

Commit

Permalink
feat: get provisions working
Browse files Browse the repository at this point in the history
implement a test that represents my current best understanding of how provisions should work for now.
  • Loading branch information
travis committed May 5, 2023
1 parent cd35397 commit 123137d
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 59 deletions.
31 changes: 27 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions upload-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@web3-storage/w3infra-ucan-invocation": "*",
"multiformats": "^11.0.1",
"prom-client": "^14.2.0",
"streaming-iterables": "^7.1.0",
"uint8arrays": "^4.0.2"
},
"devDependencies": {
Expand Down
29 changes: 20 additions & 9 deletions upload-api/tables/provisions.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,27 @@ export function useProvisionsTable (dynamoDb, tableName, services) {
provider: item.provider,
sponsor: item.account,
}
const hasProvider = await hasStorageProvider(dynamoDb, tableName, row.consumer)
if (hasProvider) {
return new ConflictError({
message: `Space ${row.consumer} cannot be provisioned with ${row.provider}: it already has a provider`
})
try {
await dynamoDb.send(new PutItemCommand({
TableName: tableName,
Item: marshall(row),
ConditionExpression: `attribute_not_exists(consumer) OR ((cid = :cid) AND (consumer = :consumer) AND (provider = :provider) AND (sponsor = :sponsor))`,
ExpressionAttributeValues: {
':cid': { 'S': row.cid },
':consumer': { 'S': row.consumer },
':provider': { 'S': row.provider },
':sponsor': { 'S': row.sponsor }
}
}))
} catch (error) {
if (error instanceof Error && error.message === 'The conditional request failed') {
return new ConflictError({
message: `Space ${row.consumer} cannot be provisioned with ${row.provider}: it already has a provider`
})
} else {
throw error
}
}
await dynamoDb.send(new PutItemCommand({
TableName: tableName,
Item: marshall(row)
}))
return {}
},

Expand Down
80 changes: 34 additions & 46 deletions upload-api/test/service/provisions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '../helpers/resources.js'
import { useProvisionsTable } from '../../tables/provisions.js'
import * as principal from '@ucanto/principal'
import {Signer} from '@ucanto/principal/ed25519'
import { Signer } from '@ucanto/principal/ed25519'
import { Provider } from '@web3-storage/capabilities'
import { CID } from 'multiformats'
import { provisionsTableProps } from '../../tables/index.js'
Expand All @@ -23,7 +23,6 @@ test.before(async (t) => {
})

/**
* TODO: migrate back to test in w3up access-api/test/provisions.test.js
*/
test('should persist provisions', async (t) => {
const { dynamo, service } = t.context
Expand All @@ -32,65 +31,54 @@ test('should persist provisions', async (t) => {
await createTable(dynamo, provisionsTableProps),
[service.did()]
)
const count = 2 + Math.round(Math.random() * 3)
const spaceA = await principal.ed25519.generate()
const [firstProvision, ...lastProvisions] = await Promise.all(
Array.from({ length: count }).map(async () => {
const issuerKey = await principal.ed25519.generate()
const issuer = issuerKey.withDID('did:mailto:example.com:foo')
const invocation = await Provider.add
.invoke({
issuer,
audience: issuer,
with: issuer.did(),
nb: {
consumer: spaceA.did(),
provider: 'did:web:web3.storage:providers:w3up-alpha',
},
})
.delegate()
/** @type {import('../../access-types').Provision<'did:web:web3.storage:providers:w3up-alpha'>} */
const provision = {
invocation,
space: spaceA.did(),
const issuerKey = await principal.ed25519.generate()
const issuer = issuerKey.withDID('did:mailto:example.com:foo')
const invocation = await Provider.add
.invoke({
issuer,
audience: issuer,
with: issuer.did(),
nb: {
consumer: spaceA.did(),
provider: 'did:web:web3.storage:providers:w3up-alpha',
account: issuer.did(),
}
return provision
},
})
)
.delegate()
/** @type {import('../../access-types').Provision<'did:web:web3.storage:providers:w3up-alpha'>} */
const provision = {
invocation,
space: spaceA.did(),
provider: 'did:web:web3.storage:providers:w3up-alpha',
account: issuer.did(),
}

t.deepEqual(await storage.count(), BigInt(0))

// TODO: I think this should fail because all of the provisions in lastProvisions have the same space and provider?!
await Promise.all(lastProvisions.map((p) => storage.put(p)))
t.deepEqual(await storage.count(), BigInt(lastProvisions.length))
const result = await storage.put(provision)
t.falsy(result.error, 'adding a provision failed')
t.deepEqual(await storage.count(), BigInt(1))

const spaceHasStorageProvider = await storage.hasStorageProvider(
spaceA.did()
)
t.deepEqual(spaceHasStorageProvider, true)

// ensure no error if we try to store same provision twice
// all of lastProvisions are duplicate, but firstProvision is new so that should be added
await storage.put(lastProvisions[0])
await storage.put(firstProvision)
t.deepEqual(await storage.count(), BigInt(count))
const dupeResult = await storage.put(provision)
t.falsy(dupeResult.error, 'putting the same provision twice did not succeed')
t.deepEqual(await storage.count(), BigInt(1))

// but if we try to store the same provision (same `cid`) with different
// fields derived from invocation, it should error
const modifiedFirstProvision = {
...firstProvision,
space: /** @type {const} */ ('did:key:foo'),
account: /** @type {const} */ ('did:mailto:example.com:foo'),
// note this type assertion is wrong, but useful to set up the test
const modifiedProvision = {
...provision,
provider: /** @type {import('@ucanto/interface').DID<'web'>} */ (
'did:provider:foo'
),
}
const result = await storage.put(modifiedFirstProvision)
t.is(
result.error && result.name,
'ConflictError',
'cannot put with same cid but different derived fields'
)

// ensure no error if we try to store a provision for a consumer that already has a provider
const modifiedResult = await storage.put(modifiedProvision)
t.truthy(modifiedResult.error, 'provisioning for a consumer who already has a provider succeeded and should not have!')
t.deepEqual(await storage.count(), BigInt(1))
})

0 comments on commit 123137d

Please sign in to comment.