Matthieu Sieben b934b396b1
Client SDK rework (#2483)
* feat(api): support creation of oauth based AtpAgents

* oauth: misc fixes for confidential clients

* fix(xprc): remove ReadableStream.from polyfill

* OAuth docs tweaks (#2679)

* OAuth: clarification about client_name being shown

* OAuth: re-write handle resolution privacy concern

* avoid relying on ReadableStream.from in xrpc-server tests

* feat(oauth-types): expose "ALLOW_UNSECURE_ORIGINS" constant

* feat(handle-resolver): expose "AtprotoIdentityDidMethods" type

* fix(oauth-client): ensure that the oauth metadata document contains client_id_metadata_document_supported

* fix(oauth-types): prevent unknown query string in loopback client id

* fix(identity-resolver): check that handle is in did doc's "alsoKnownAs"

* feat(oauth-client:oauth-resolver): allow logging in using either the PDS URL or Entryway URL

* fix(oauth-client): return better error in case of invalid "oauth-protected-resource" status code

* refactor(did): group atproto specific checks in own

* feat(api): relax typing of "appLabelers" and "labelers" AtpClient properties

* allow any did as labeller (for tests mainly)

* fix(api): allow to override "atproto-proxy" on a per-request basis

* remove release candidate versions from changelog

* update changeset for api and xrpc packages

* Add missing changeset

* revert RC versions

* Proper wording in OAUTH.md api example

* remove "pre" changeset file

* xrpc: restore original behavior of setHEader and unsetHeader

* docs: add comment for XrpcClient 's constructor arg

* feat(api): expose "schemas" publicly

* feat(api): allow customizing the whatwg fetch function of the AtpAgent

* docs(api): improve migration docs

* docs: change reference to BskyAgent to AtpAgent

* docs: mention the breaking change regarding setSessionPersistHandler

* fix(api): better split AtpClient concerns

* fix(xrpc): remove unused import

* refactor(api): simplify class hierarchu by removeing AtpClient

* fix(api): mock proper method for facets detection

* restore ability to restore session asynchronously

* feat(api): allow instantiating Agent with same argument as super class

* docs(api): properly extend Agent class

* style(xrpc): var name

* docs(api): remove "async" to header getter

---------

Co-authored-by: Devin Ivy <devinivy@gmail.com>
Co-authored-by: bnewbold <bnewbold@robocracy.org>
Co-authored-by: Hailey <me@haileyok.com>
2024-08-12 19:57:21 +02:00

164 lines
5.6 KiB
TypeScript

import { SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env'
import { AtpAgent } from '@atproto/api'
import { forSnapshot } from './_util'
describe('team management', () => {
let network: TestNetwork
let adminAgent: AtpAgent
let triageAgent: AtpAgent
let sc: SeedClient
beforeAll(async () => {
network = await TestNetwork.create({
dbPostgresSchema: 'ozone_team_test',
})
adminAgent = network.pds.getClient()
sc = network.getSeedClient()
await basicSeed(sc)
await network.processAll()
await network.ozone.addAdminDid(sc.dids.alice)
await network.ozone.addModeratorDid(sc.dids.bob)
await network.ozone.addTriageDid(sc.dids.carol)
await adminAgent.login({
identifier: sc.accounts[sc.dids.alice].handle,
password: sc.accounts[sc.dids.alice].password,
})
triageAgent = network.pds.getClient()
await triageAgent.login({
identifier: sc.accounts[sc.dids.carol].handle,
password: sc.accounts[sc.dids.carol].password,
})
})
afterAll(async () => {
await network.close()
})
describe('listMembers', () => {
it('allows all members to list all members', async () => {
const [{ data: forAdmin }, { data: forTriage }] = await Promise.all([
adminAgent.api.tools.ozone.team.listMembers({}),
triageAgent.api.tools.ozone.team.listMembers({}),
])
expect(forSnapshot(forAdmin.members)).toMatchSnapshot()
expect(forSnapshot(forTriage.members)).toMatchSnapshot()
// Validate that the list looks the same to both admin and triage members
expect(forAdmin.members.length).toEqual(forTriage.members.length)
})
})
describe('listMembers', () => {
it('allows all members to list all members', async () => {
const [{ data: forAdmin }, { data: forTriage }] = await Promise.all([
adminAgent.api.tools.ozone.team.listMembers({}),
triageAgent.api.tools.ozone.team.listMembers({}),
])
expect(forSnapshot(forAdmin.members)).toMatchSnapshot()
expect(forSnapshot(forTriage.members)).toMatchSnapshot()
// Validate that the list looks the same to both admin and triage members
expect(forAdmin.members.length).toEqual(forTriage.members.length)
})
})
describe('addMember', () => {
const newMemberData = {
did: 'did:plc:newMember',
role: 'tools.ozone.team.defs#roleAdmin',
disabled: false,
}
it('only allows admins to add member', async () => {
await expect(
triageAgent.api.tools.ozone.team.addMember(newMemberData),
).rejects.toThrow('Must be an admin to add a member')
const { data: newMember } =
await adminAgent.api.tools.ozone.team.addMember(newMemberData)
expect(forSnapshot(newMember)).toMatchSnapshot()
})
it('throws error when trying to add existing member', async () => {
await expect(
adminAgent.api.tools.ozone.team.addMember(newMemberData),
).rejects.toThrow('member already exists')
})
})
describe('deleteMember', () => {
it('only allows admins to delete members', async () => {
const {
data: { members: initialMembers },
} = await adminAgent.api.tools.ozone.team.listMembers({})
await expect(
triageAgent.api.tools.ozone.team.deleteMember({
did: sc.dids.bob,
}),
).rejects.toThrow('Must be an admin to delete a member')
await adminAgent.api.tools.ozone.team.deleteMember({
did: sc.dids.bob,
})
const {
data: { members: membersAfterDelete },
} = await adminAgent.api.tools.ozone.team.listMembers({})
expect(membersAfterDelete.length).toEqual(initialMembers.length - 1)
expect(membersAfterDelete.map(({ did }) => did)).not.toContain(
sc.dids.bob,
)
})
it('throws error when trying to remove non-existent member', async () => {
await expect(
adminAgent.api.tools.ozone.team.deleteMember({
did: 'did:plc:test',
}),
).rejects.toThrow('member not found')
})
})
describe('updateMember', () => {
it('allows admins to update member', async () => {
const getCarol = async () => {
const {
data: { members },
} = await adminAgent.api.tools.ozone.team.listMembers({})
return members.find(({ did }) => did === sc.dids.carol)
}
await expect(
triageAgent.api.tools.ozone.team.updateMember({
disabled: false,
did: sc.dids.carol,
role: 'tools.ozone.team.defs#roleAdmin',
}),
).rejects.toThrow('Must be an admin to update a member')
await adminAgent.api.tools.ozone.team.updateMember({
did: sc.dids.carol,
role: 'tools.ozone.team.defs#roleAdmin',
})
const carolAfterRoleChange = await getCarol()
expect(carolAfterRoleChange?.role).toEqual(
'tools.ozone.team.defs#roleAdmin',
)
// Verify that params that we didn't send did not get updated
expect(carolAfterRoleChange?.disabled).toEqual(false)
await adminAgent.api.tools.ozone.team.updateMember({
did: sc.dids.carol,
disabled: true,
})
const carolAfterDisable = await getCarol()
expect(carolAfterDisable?.disabled).toEqual(true)
// Verify that params that we didn't send did not get updated
expect(carolAfterDisable?.role).toEqual('tools.ozone.team.defs#roleAdmin')
})
it('throws error when trying to update non-existent member', async () => {
await expect(
adminAgent.api.tools.ozone.team.updateMember({
disabled: false,
did: 'did:plc:test',
role: 'tools.ozone.team.defs#roleAdmin',
}),
).rejects.toThrow('member not found')
})
})
})