@storacha/client
Version:
Client for the storacha.network w3up api
280 lines • 14.2 kB
JavaScript
import * as Test from './test.js';
import * as Account from '../src/account.js';
import * as Space from '../src/space.js';
import * as Result from '../src/result.js';
import { DIDMailto } from '../src/capability/access.js';
/**
* @type {Test.Suite}
*/
export const testAccount = Test.withContext({
'list accounts': async (assert, { client, mail, grantAccess }) => {
const email = 'alice@web.mail';
assert.deepEqual(Account.list(client), {}, 'no accounts yet');
const login = Account.login(client, email);
const message = await mail.take();
assert.deepEqual(message.to, email);
await grantAccess(message);
const session = await login;
assert.equal(session.error, undefined);
assert.equal(session.ok?.did(), Account.fromEmail(email));
assert.equal(session.ok?.toEmail(), email);
assert.equal(session.ok?.proofs.length, 2);
assert.deepEqual(Account.list(client), {}, 'no accounts have been saved');
await session.ok?.save();
const accounts = Account.list(client);
assert.deepEqual(Object.values(accounts).length, 1);
assert.ok(accounts[Account.fromEmail(email)]);
const account = accounts[Account.fromEmail(email)];
assert.equal(account.toEmail(), email);
assert.equal(account.did(), Account.fromEmail(email));
assert.equal(account.proofs.length, 2);
},
'two logins': async (assert, { client, mail, grantAccess }) => {
const aliceEmail = 'alice@web.mail';
const bobEmail = 'bob@web.mail';
assert.deepEqual(Account.list(client), {}, 'no accounts yet');
const aliceLogin = Account.login(client, aliceEmail);
await grantAccess(await mail.take());
const alice = await aliceLogin;
assert.deepEqual(alice.ok?.toEmail(), aliceEmail);
assert.deepEqual(Account.list(client), {}, 'no accounts have been saved');
const saveAlice = await alice.ok?.save();
assert.equal(saveAlice?.error, undefined);
const one = Account.list(client);
assert.deepEqual(Object.values(one).length, 1);
assert.ok(one[Account.fromEmail(aliceEmail)], 'alice in the account list');
const bobLogin = Account.login(client, bobEmail);
await grantAccess(await mail.take());
const bob = await bobLogin;
assert.deepEqual(bob.ok?.toEmail(), bobEmail);
await bob.ok?.save();
const two = Account.list(client);
assert.deepEqual(Object.values(two).length, 2);
assert.ok(two[Account.fromEmail(aliceEmail)].toEmail(), aliceEmail);
assert.ok(two[Account.fromEmail(bobEmail)].toEmail(), bobEmail);
},
'login idempotence': async (assert, { client, mail, grantAccess }) => {
const email = 'alice@web.mail';
const login = client.login(email);
await grantAccess(await mail.take());
const alice = await login;
assert.deepEqual(Object.keys(client.accounts()), [alice.did()], 'no accounts have been saved');
const retry = await client.login(email);
assert.deepEqual(alice.toJSON(), retry.toJSON(), 'same account view is returned');
const loginResult = await Account.login(client, email);
assert.deepEqual(alice.toJSON(), loginResult.ok?.toJSON(), 'same account is returned with low level API');
},
'client.login': async (assert, { client, mail, grantAccess }) => {
const account = client.login('alice@web.mail');
await grantAccess(await mail.take());
const alice = await account;
assert.deepEqual(alice.toEmail(), 'alice@web.mail');
const accounts = client.accounts();
assert.deepEqual(Object.keys(accounts), [alice.did()]);
},
'create account and provision space': async (assert, { client, mail, grantAccess }) => {
const space = await client.createSpace('test', {
skipGatewayAuthorization: true,
});
const mnemonic = space.toMnemonic();
const { signer } = await Space.fromMnemonic(mnemonic, { name: 'import' });
assert.deepEqual(space.signer.encode(), signer.encode(), 'arrived to same signer');
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);
const result = await account.provision(space.did());
assert.equal(result.error, undefined);
// authorize agent to use space
const proof = await space.createAuthorization(client.agent, {
access: { 'space/info': {} },
expiration: Infinity,
});
await client.addSpace(proof);
const info = await client.capability.space.info(space.did());
assert.deepEqual(info, {
did: space.did(),
providers: [client.agent.connection.id.did()],
});
},
'multi device workflow': async (asserts, { connect, mail, grantAccess }) => {
const laptop = await connect();
const space = await laptop.createSpace('main', {
skipGatewayAuthorization: true,
});
// want to provision space ?
const email = 'alice@web.mail';
const login = Account.login(laptop, email);
// confirm by clicking a link
await grantAccess(await mail.take());
const account = Result.try(await login);
// Authorized account can provision space
Result.try(await account.provision(space.did()));
// Want to setup a recovery for this space ?
const recovery = await space.createRecovery(account.did());
// Authorize laptop to use the space, we need to do it in order
// to be able to store the recovery delegation in the space.
await laptop.addSpace(await space.createAuthorization(laptop.agent));
// Store delegation to the account so it can be used for recovery
await laptop.capability.access.delegate({
delegations: [recovery],
});
// now connect with a second device
const phone = await connect();
const phoneLogin = Account.login(phone, email);
// confirm by clicking a link
await grantAccess(await mail.take());
const session = Result.try(await phoneLogin);
// save session on the phone
Result.try(await session.save());
const result = await phone.capability.space.info(space.did());
asserts.deepEqual(result.did, space.did());
},
'setup recovery': async (assert, { client, mail, grantAccess }) => {
const space = await client.createSpace('test', {
skipGatewayAuthorization: true,
});
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);
Result.try(await account.provision(space.did()));
const recovery = await space.createRecovery(account.did());
const share = await client.capability.access.delegate({
space: space.did(),
delegations: [recovery],
proofs: [await space.createAuthorization(client)],
});
assert.equal(share.error, undefined);
assert.deepEqual(client.spaces(), []);
assert.deepEqual(client.spaces().length, 0, 'no spaces had been added');
// waiting for a sec so that request CID will come out different
// otherwise we will find previous authorization which does not
// have the space delegation yet.
await new Promise((resolve) => setTimeout(resolve, 1000));
// This is not a great flow but to fix this we need a new to upgrade
// ucanto and then pull delegations for each account.
const secondLogin = Account.login(client, email);
await grantAccess(await mail.take());
const secondAccount = Result.try(await secondLogin);
Result.try(await secondAccount.save());
assert.deepEqual(client.spaces().length, 1, 'spaces had been added');
},
'check and set account plan': async (assert, { client, mail, grantAccess, plansStorage }) => {
const login = Account.login(client, 'alice@web.mail');
await grantAccess(await mail.take());
const account = Result.try(await login);
const { error } = await account.plan.get();
assert.ok(error);
Result.unwrap(await plansStorage.initialize(account.did(), 'stripe:123xyz', 'did:web:free.storacha.network'));
const { ok: plan } = await account.plan.get({ nonce: '2' });
assert.ok(plan?.product, 'did:web:free.storacha.network');
Result.unwrap(await account.plan.set('did:web:lite.storacha.network'));
const { ok: newPlan } = await account.plan.get({ nonce: '3' });
assert.ok(newPlan?.product, 'did:web:lite.storacha.network');
},
'create plan admin session': async (assert, { client, mail, grantAccess, plansStorage }) => {
const accountEmail = 'alice@web.mail';
const accountDID = DIDMailto.fromEmail(accountEmail);
const login = Account.login(client, accountEmail);
await grantAccess(await mail.take());
const account = Result.try(await login);
await account.save();
Result.try(await plansStorage.initialize(account.did(), 'stripe:123xyz', 'did:web:example.com'));
const { ok } = await account.plan.createAdminSession(accountDID, 'https://example.com');
assert.ok(ok);
assert.ok(ok?.url);
},
'check account subscriptions': async (assert, { client, mail, grantAccess }) => {
const space = await client.createSpace('test', {
skipGatewayAuthorization: true,
});
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);
Result.try(await account.provision(space.did()));
const subs = Result.unwrap(await account.plan.subscriptions());
assert.equal(subs.results.length, 1);
assert.equal(subs.results[0].provider, client.defaultProvider());
assert.deepEqual(subs.results[0].consumers, [space.did()]);
assert.equal(typeof subs.results[0].subscription, 'string');
},
'space.save': async (assert, { client }) => {
const space = await client.createSpace('test', {
skipGatewayAuthorization: true,
});
assert.deepEqual(client.spaces(), []);
const result = await space.save();
assert.ok(result.ok);
const spaces = client.spaces();
assert.deepEqual(spaces.length, 1);
assert.deepEqual(spaces[0].did(), space.did());
assert.deepEqual(client.currentSpace()?.did(), space.did());
},
waitForPaymentPlan: {
'should wait for a payment plan to be selected': async (assert, { client, mail, grantAccess }) => {
const email = 'alice@web.mail';
const login = Account.login(client, email);
await grantAccess(await mail.take());
const account = Result.try(await login);
let callCount = 0;
// Mock the get method to simulate a plan being selected after some time
// @ts-expect-error
account.plan.get = async () => {
callCount++;
if (callCount > 2) {
return { ok: { product: 'did:web:example.com' } };
}
return { ok: false };
};
const result = await account.plan.wait({ interval: 100, timeout: 1000 });
assert.deepEqual(result.product, 'did:web:example.com');
},
'should throw an error if there is an issue retrieving the payment plan': async (assert, { client, mail, grantAccess }) => {
const email = 'alice@web.mail';
const login = Account.login(client, email);
await grantAccess(await mail.take());
const account = Result.try(await login);
// @ts-expect-error
account.plan.get = async () => Promise.resolve({ error: 'Some error' });
await assert.rejects(account.plan.wait({ interval: 100, timeout: 1000 }), {
message: 'Error retrieving payment plan: "Some error"',
});
},
'should throw a timeout error if the payment plan selection takes too long': async (assert, { client, mail, grantAccess }) => {
const email = 'alice@web.mail';
const login = Account.login(client, email);
await grantAccess(await mail.take());
const account = Result.try(await login);
// @ts-expect-error
account.plan.get = async () => Promise.resolve({ ok: false });
await assert.rejects(account.plan.wait({ interval: 100, timeout: 500 }), {
message: 'Timeout: Payment plan selection took too long.',
});
},
'should throw an error when the abort signal is aborted': async (assert, { client, mail, grantAccess }) => {
const abortController = new AbortController();
const signal = abortController.signal;
const email = 'alice@web.mail';
const login = Account.login(client, email);
await grantAccess(await mail.take());
const account = Result.try(await login);
// @ts-expect-error
account.plan.get = async () => Promise.resolve({ ok: false });
// Abort the signal after a short delay
setTimeout(() => abortController.abort(), 100);
await assert.rejects(account.plan.wait({ signal }), {
message: 'Aborted: Payment plan selection was aborted.',
});
},
},
});
Test.test({ Account: testAccount });
//# sourceMappingURL=account.test.js.map