Foysal Ahamed 22d039a229
Ozone sets (#2636)
*  Initial implementation of sets api on ozone

*  Introduce sortDirection to querySets

* 🧹 Cleanup and refactor

*  Align setView for response

* ♻️ Rename and add specific error

* 🐛 Cleanup unnecessary check that is covered by lexicon

*  Rename remove to delete and add set suffix

*  Use id and createdAt for values pagination

*  Add index on createdAt for query perf and other cleanups

* 🐛 Set createdAt when inserting values

* 📝 Add changeset

*  Add index on setId and createdAt
2024-10-08 19:16:09 +02:00

247 lines
6.7 KiB
TypeScript

import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env'
import AtpAgent, {
ToolsOzoneSetDefs,
ToolsOzoneSetQuerySets,
} from '@atproto/api'
import { forSnapshot } from './_util'
import { ids } from '../src/lexicon/lexicons'
describe('ozone-sets', () => {
let network: TestNetwork
let agent: AtpAgent
let sc: SeedClient
const sampleSet1 = {
name: 'test-set-1',
description: 'Test set 1',
}
const sampleSet2 = {
name: 'test-set-2',
}
const sampleSet3 = {
name: 'another-set',
description: 'Another test set',
}
const upsertSet = async (set: ToolsOzoneSetDefs.Set) => {
const { data } = await agent.tools.ozone.set.upsertSet(set, {
encoding: 'application/json',
headers: await network.ozone.modHeaders(
ids.ToolsOzoneSetUpsertSet,
'admin',
),
})
return data
}
const removeSet = async (name: string) => {
await agent.tools.ozone.set.deleteSet(
{ name },
{
encoding: 'application/json',
headers: await network.ozone.modHeaders(
ids.ToolsOzoneSetDeleteSet,
'admin',
),
},
)
}
const addValues = async (name: string, values: string[]) => {
await agent.tools.ozone.set.addValues(
{ name, values },
{
encoding: 'application/json',
headers: await network.ozone.modHeaders(
ids.ToolsOzoneSetAddValues,
'admin',
),
},
)
}
const getValues = async (name: string, limit?: number, cursor?: string) => {
const { data } = await agent.tools.ozone.set.getValues(
{ name, limit, cursor },
{
headers: await network.ozone.modHeaders(
ids.ToolsOzoneSetGetValues,
'moderator',
),
},
)
return data
}
const querySets = async (params: ToolsOzoneSetQuerySets.QueryParams) => {
const { data } = await agent.tools.ozone.set.querySets(params, {
headers: await network.ozone.modHeaders(
ids.ToolsOzoneSetQuerySets,
'moderator',
),
})
return data
}
beforeAll(async () => {
network = await TestNetwork.create({
dbPostgresSchema: 'ozone_sets',
})
agent = network.ozone.getClient()
sc = network.getSeedClient()
await basicSeed(sc)
await network.processAll()
})
afterAll(async () => {
await network.close()
})
describe('querySets', () => {
beforeAll(async () => {
await Promise.all([
upsertSet(sampleSet1),
upsertSet(sampleSet2),
upsertSet(sampleSet3),
])
})
afterAll(async () => {
await Promise.all([
removeSet(sampleSet1.name),
removeSet(sampleSet2.name),
removeSet(sampleSet3.name),
])
})
it('returns all sets when no parameters are provided', async () => {
const result = await querySets({})
expect(result.sets.length).toBe(3)
expect(forSnapshot(result.sets)).toMatchSnapshot()
})
it('limits the number of returned sets', async () => {
const result = await querySets({ limit: 2 })
expect(result.sets.length).toBe(2)
expect(result.cursor).toBeDefined()
})
it('returns sets after the cursor', async () => {
const firstPage = await querySets({ limit: 2 })
const secondPage = await querySets({ cursor: firstPage.cursor })
expect(secondPage.sets.length).toBe(1)
expect(secondPage.sets[0].name).toBe('test-set-2')
})
it('filters sets by name prefix', async () => {
const result = await querySets({ namePrefix: 'test-' })
expect(result.sets.length).toBe(2)
expect(result.sets.map((s) => s.name)).toEqual([
'test-set-1',
'test-set-2',
])
})
it('sorts sets by given column and direction', async () => {
const sortedByName = await querySets({ sortBy: 'name' })
expect(sortedByName.sets.map((s) => s.name)).toEqual([
'another-set',
'test-set-1',
'test-set-2',
])
const reverseSortedByName = await querySets({
sortBy: 'name',
sortDirection: 'desc',
})
expect(reverseSortedByName.sets.map((s) => s.name)).toEqual([
'test-set-2',
'test-set-1',
'another-set',
])
})
})
describe('upsertSet', () => {
afterAll(async () => {
await removeSet('new-test-set')
})
it('creates a new set', async () => {
const result = await upsertSet({
name: 'new-test-set',
description: 'A new test set',
})
expect(forSnapshot(result)).toMatchSnapshot()
})
it('updates an existing set', async () => {
const result = await upsertSet({
name: 'new-test-set',
description: 'Updated description',
})
expect(forSnapshot(result)).toMatchSnapshot()
})
it('allows setting empty description', async () => {
const result = await upsertSet({
name: 'new-test-set',
description: '',
})
expect(result.description).toBeUndefined()
})
})
describe('addValues', () => {
beforeAll(async () => {
await upsertSet(sampleSet1)
await upsertSet(sampleSet2)
})
afterAll(async () => {
await removeSet(sampleSet1.name)
await removeSet(sampleSet2.name)
})
it('adds new values to an existing set', async () => {
const newValues = ['value1', 'value2', 'value3']
await addValues(sampleSet1.name, newValues)
const result = await getValues(sampleSet1.name)
expect(result.values).toEqual(expect.arrayContaining(newValues))
})
it('does not duplicate existing values', async () => {
const initialValues = ['initial1', 'initial2']
await addValues(sampleSet2.name, initialValues)
const newValues = ['initial2', 'new1', 'new2']
await addValues(sampleSet2.name, newValues)
const result = await getValues(sampleSet2.name)
expect(result.values).toEqual(
expect.arrayContaining([...initialValues, 'new1', 'new2']),
)
expect(result.values.filter((v) => v === 'initial2').length).toBe(1)
})
})
describe('getValues', () => {
beforeAll(async () => {
await upsertSet(sampleSet1)
})
afterAll(async () => {
await removeSet(sampleSet1.name)
})
it('paginates values from a set', async () => {
const allValues = Array.from({ length: 9 }, (_, i) => `value${i}`)
await addValues(sampleSet1.name, allValues)
const firstPage = await getValues(sampleSet1.name, 3)
const secondPage = await getValues(sampleSet1.name, 3, firstPage.cursor)
const lastPage = await getValues(sampleSet1.name, 3, secondPage.cursor)
expect(firstPage.values).toEqual(allValues.slice(0, 3))
expect(secondPage.values).toEqual(allValues.slice(3, 6))
expect(lastPage.values).toEqual(allValues.slice(6, 9))
})
})
})