Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions routes/internal/caches.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { z } from 'zod'
import { getDatabase } from '~/lib/db'

const querySchema = z.object({
key: z.string().optional(),
version: z.string().optional(),
page: z.coerce.number().int().min(0).default(0),
limit: z.coerce.number().int().min(1).max(100).default(20),
})

export default defineEventHandler(async (event) => {
const parsed = querySchema.safeParse(getQuery(event))
if (!parsed.success)
throw createError({ statusCode: 400, statusMessage: parsed.error.message })

const { key, version, page, limit } = parsed.data
const db = await getDatabase()

let query = db
.selectFrom('cache_entries')
.selectAll()
.orderBy('updatedAt', 'desc')
.limit(limit)
.offset(page * limit)

if (key) query = query.where('key', 'like', `${key}%`)
if (version) query = query.where('version', '=', version)

const [items, total] = await Promise.all([
query.execute(),
db
.selectFrom('cache_entries')
.select((eb) => eb.fn.countAll<number>().as('count'))
.$if(!!key, (q) => q.where('key', 'like', `${key}%`))
.$if(!!version, (q) => q.where('version', '=', version!))
.executeTakeFirstOrThrow()
.then((r) => Number(r.count)),
])

return { items, total, page, limit }
})
70 changes: 70 additions & 0 deletions tests/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,76 @@ import { TEST_TEMP_DIR } from './setup'
const testFilePath = path.join(TEST_TEMP_DIR, 'test.bin')

const MB = 1024 * 1024
const BASE_URL = process.env.API_BASE_URL!

describe('GET /internal/caches', () => {
beforeAll(async () => {
process.env.ACTIONS_CACHE_SERVICE_V2 = 'true'
process.env.ACTIONS_RUNTIME_TOKEN = 'mock-runtime-token'

// seed two distinct cache entries
const buf = crypto.randomBytes(1024)
await fs.writeFile(testFilePath, buf)
await saveCache([testFilePath], 'list-test-key-alpha')
await saveCache([testFilePath], 'list-test-key-beta')
})
afterAll(() => {
delete process.env.ACTIONS_CACHE_SERVICE_V2
delete process.env.ACTIONS_RUNTIME_TOKEN
})

test('returns all entries with default pagination', async () => {
const res = await fetch(`${BASE_URL}/internal/caches`)
expect(res.status).toBe(200)
const body = await res.json() as { items: unknown[]; total: number; page: number; limit: number }
expect(body.page).toBe(0)
expect(body.limit).toBe(20)
expect(typeof body.total).toBe('number')
expect(Array.isArray(body.items)).toBe(true)
expect(body.total).toBeGreaterThanOrEqual(2)
})

test('filters by key prefix', async () => {
const res = await fetch(`${BASE_URL}/internal/caches?key=list-test-key-alpha`)
expect(res.status).toBe(200)
const body = await res.json() as { items: Array<{ key: string }>; total: number }
expect(body.total).toBeGreaterThanOrEqual(1)
for (const item of body.items) {
expect(item.key).toMatch(/^list-test-key-alpha/)
}
})

test('filters by version returns only matching version', async () => {
// fetch the version of a known entry from the listing, then filter by it
const all = await fetch(`${BASE_URL}/internal/caches?key=list-test-key-alpha`)
const allBody = await all.json() as { items: Array<{ key: string; version: string }> }
const knownVersion = allBody.items[0]!.version

const res = await fetch(`${BASE_URL}/internal/caches?version=${knownVersion}`)
expect(res.status).toBe(200)
const body = await res.json() as { items: Array<{ version: string }> }
for (const item of body.items) {
expect(item.version).toBe(knownVersion)
}
})

test('pagination with limit=1', async () => {
const resP0 = await fetch(`${BASE_URL}/internal/caches?limit=1&page=0`)
const resP1 = await fetch(`${BASE_URL}/internal/caches?limit=1&page=1`)
expect(resP0.status).toBe(200)
expect(resP1.status).toBe(200)
const bodyP0 = await resP0.json() as { items: Array<{ id: string }> }
const bodyP1 = await resP1.json() as { items: Array<{ id: string }> }
expect(bodyP0.items).toHaveLength(1)
expect(bodyP1.items).toHaveLength(1)
expect(bodyP0.items[0]!.id).not.toBe(bodyP1.items[0]!.id)
})

test('returns 400 for invalid query params', async () => {
const res = await fetch(`${BASE_URL}/internal/caches?limit=999`)
expect(res.status).toBe(400)
})
})

describe(`save and restore cache with @actions/cache package`, () => {
beforeAll(() => {
Expand Down