mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-15 01:12:11 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d5b2b542c | ||
|
|
401ba2b533 | ||
|
|
16ee2ce871 | ||
|
|
652ee445f7 | ||
|
|
f7cc94f861 | ||
|
|
1db2d840a4 |
+7
-3
@@ -71,17 +71,21 @@ export const downloadZip = httpAction(async (ctx, request) => {
|
||||
}
|
||||
|
||||
const skill = skillResult.skill
|
||||
let version = skillResult.latestVersion
|
||||
let version = skill.latestVersionId
|
||||
? await ctx.runQuery(internal.skills.getVersionByIdInternal, {
|
||||
versionId: skill.latestVersionId,
|
||||
})
|
||||
: null
|
||||
|
||||
if (versionParam) {
|
||||
version = await ctx.runQuery(api.skills.getVersionBySkillAndVersion, {
|
||||
version = await ctx.runQuery(internal.skills.getVersionBySkillAndVersionInternal, {
|
||||
skillId: skill._id,
|
||||
version: versionParam,
|
||||
})
|
||||
} else if (tagParam) {
|
||||
const versionId = skill.tags[tagParam]
|
||||
if (versionId) {
|
||||
version = await ctx.runQuery(api.skills.getVersionById, { versionId })
|
||||
version = await ctx.runQuery(internal.skills.getVersionByIdInternal, { versionId })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1444,7 +1444,7 @@ describe('httpApiV1 handlers', () => {
|
||||
})
|
||||
|
||||
it('returns raw file content', async () => {
|
||||
const version = {
|
||||
const internalVersion = {
|
||||
version: '1.0.0',
|
||||
createdAt: 1,
|
||||
changelog: 'c',
|
||||
@@ -1459,19 +1459,28 @@ describe('httpApiV1 handlers', () => {
|
||||
],
|
||||
softDeletedAt: undefined,
|
||||
}
|
||||
const runQuery = vi.fn().mockResolvedValue({
|
||||
skill: {
|
||||
_id: 'skills:1',
|
||||
slug: 'demo',
|
||||
displayName: 'Demo',
|
||||
summary: 's',
|
||||
tags: {},
|
||||
stats: {},
|
||||
createdAt: 1,
|
||||
updatedAt: 2,
|
||||
},
|
||||
latestVersion: version,
|
||||
owner: null,
|
||||
const runQuery = vi.fn(async (_query: unknown, args: Record<string, unknown>) => {
|
||||
if ('slug' in args) {
|
||||
return {
|
||||
skill: {
|
||||
_id: 'skills:1',
|
||||
slug: 'demo',
|
||||
displayName: 'Demo',
|
||||
summary: 's',
|
||||
tags: {},
|
||||
stats: {},
|
||||
createdAt: 1,
|
||||
updatedAt: 2,
|
||||
latestVersionId: 'skillVersions:1',
|
||||
},
|
||||
latestVersion: { _id: 'skillVersions:1', version: '1.0.0' },
|
||||
owner: null,
|
||||
}
|
||||
}
|
||||
if ('versionId' in args) {
|
||||
return internalVersion
|
||||
}
|
||||
return null
|
||||
})
|
||||
const runMutation = vi.fn().mockResolvedValue(okRate())
|
||||
const storage = {
|
||||
@@ -1487,7 +1496,7 @@ describe('httpApiV1 handlers', () => {
|
||||
})
|
||||
|
||||
it('returns 413 when raw file too large', async () => {
|
||||
const version = {
|
||||
const internalVersion = {
|
||||
version: '1.0.0',
|
||||
createdAt: 1,
|
||||
changelog: 'c',
|
||||
@@ -1502,19 +1511,28 @@ describe('httpApiV1 handlers', () => {
|
||||
],
|
||||
softDeletedAt: undefined,
|
||||
}
|
||||
const runQuery = vi.fn().mockResolvedValue({
|
||||
skill: {
|
||||
_id: 'skills:1',
|
||||
slug: 'demo',
|
||||
displayName: 'Demo',
|
||||
summary: 's',
|
||||
tags: {},
|
||||
stats: {},
|
||||
createdAt: 1,
|
||||
updatedAt: 2,
|
||||
},
|
||||
latestVersion: version,
|
||||
owner: null,
|
||||
const runQuery = vi.fn(async (_query: unknown, args: Record<string, unknown>) => {
|
||||
if ('slug' in args) {
|
||||
return {
|
||||
skill: {
|
||||
_id: 'skills:1',
|
||||
slug: 'demo',
|
||||
displayName: 'Demo',
|
||||
summary: 's',
|
||||
tags: {},
|
||||
stats: {},
|
||||
createdAt: 1,
|
||||
updatedAt: 2,
|
||||
latestVersionId: 'skillVersions:1',
|
||||
},
|
||||
latestVersion: { _id: 'skillVersions:1', version: '1.0.0' },
|
||||
owner: null,
|
||||
}
|
||||
}
|
||||
if ('versionId' in args) {
|
||||
return internalVersion
|
||||
}
|
||||
return null
|
||||
})
|
||||
const runMutation = vi.fn().mockResolvedValue(okRate())
|
||||
const response = await __handlers.skillsGetRouterV1Handler(
|
||||
|
||||
@@ -57,7 +57,31 @@ type ListSkillsResult = {
|
||||
nextCursor: string | null
|
||||
}
|
||||
|
||||
type SkillFile = Doc<'skillVersions'>['files'][number]
|
||||
type PublicSkillVersionFile = {
|
||||
path: string
|
||||
size: number
|
||||
sha256: string
|
||||
contentType?: string
|
||||
}
|
||||
|
||||
type PublicSkillVersionParsed = {
|
||||
license?: 'MIT-0'
|
||||
clawdis?: { os?: string[]; nix?: { plugin?: boolean; systems?: string[] } }
|
||||
}
|
||||
|
||||
type PublicSkillVersionResponse = {
|
||||
_id: Id<'skillVersions'>
|
||||
version: string
|
||||
createdAt?: number
|
||||
changelog?: string
|
||||
changelogSource?: 'auto' | 'user'
|
||||
files: PublicSkillVersionFile[]
|
||||
parsed?: PublicSkillVersionParsed
|
||||
softDeletedAt?: number
|
||||
sha256hash?: string
|
||||
vtAnalysis?: Doc<'skillVersions'>['vtAnalysis']
|
||||
llmAnalysis?: Doc<'skillVersions'>['llmAnalysis']
|
||||
}
|
||||
|
||||
type ModerationEvidence = {
|
||||
code: string
|
||||
@@ -90,8 +114,9 @@ type GetBySlugResult = {
|
||||
stats: unknown
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
latestVersionId?: Id<'skillVersions'>
|
||||
} | null
|
||||
latestVersion: Doc<'skillVersions'> | null
|
||||
latestVersion: PublicSkillVersionResponse | null
|
||||
owner: { _id: Id<'users'>; handle?: string; displayName?: string; image?: string } | null
|
||||
moderationInfo?: {
|
||||
isPendingScan: boolean
|
||||
@@ -109,20 +134,7 @@ type GetBySlugResult = {
|
||||
} | null
|
||||
|
||||
type ListVersionsResult = {
|
||||
items: Array<{
|
||||
version: string
|
||||
createdAt: number
|
||||
changelog: string
|
||||
changelogSource?: 'auto' | 'user'
|
||||
files: Array<{
|
||||
path: string
|
||||
size: number
|
||||
storageId: Id<'_storage'>
|
||||
sha256: string
|
||||
contentType?: string
|
||||
}>
|
||||
softDeletedAt?: number
|
||||
}>
|
||||
items: PublicSkillVersionResponse[]
|
||||
nextCursor: string | null
|
||||
}
|
||||
|
||||
@@ -258,7 +270,12 @@ function hasLlmDimensionWarnings(
|
||||
})
|
||||
}
|
||||
|
||||
function buildSkillSecuritySnapshot(version: Doc<'skillVersions'>): SkillSecuritySnapshot | null {
|
||||
function buildSkillSecuritySnapshot(
|
||||
version: Pick<
|
||||
PublicSkillVersionResponse,
|
||||
'sha256hash' | 'vtAnalysis' | 'llmAnalysis'
|
||||
>,
|
||||
): SkillSecuritySnapshot | null {
|
||||
const sha256hash = version.sha256hash ?? null
|
||||
const vt = version.vtAnalysis
|
||||
const llm = version.llmAnalysis
|
||||
@@ -681,10 +698,10 @@ export async function skillsGetRouterV1Handler(ctx: ActionCtx, request: Request)
|
||||
const skillResult = (await ctx.runQuery(api.skills.getBySlug, { slug })) as GetBySlugResult
|
||||
if (!skillResult?.skill) return text('Skill not found', 404, rate.headers)
|
||||
|
||||
const version = await ctx.runQuery(api.skills.getVersionBySkillAndVersion, {
|
||||
const version = (await ctx.runQuery(api.skills.getVersionBySkillAndVersion, {
|
||||
skillId: skillResult.skill._id,
|
||||
version: third,
|
||||
})
|
||||
})) as PublicSkillVersionResponse | null
|
||||
if (!version) return text('Version not found', 404, rate.headers)
|
||||
if (version.softDeletedAt) return text('Version not available', 410, rate.headers)
|
||||
const security = buildSkillSecuritySnapshot(version)
|
||||
@@ -698,7 +715,7 @@ export async function skillsGetRouterV1Handler(ctx: ActionCtx, request: Request)
|
||||
changelog: version.changelog,
|
||||
changelogSource: version.changelogSource ?? null,
|
||||
license: version.parsed?.license ?? null,
|
||||
files: version.files.map((file: SkillFile) => ({
|
||||
files: version.files.map((file) => ({
|
||||
path: file.path,
|
||||
size: file.size,
|
||||
sha256: file.sha256,
|
||||
@@ -792,16 +809,20 @@ export async function skillsGetRouterV1Handler(ctx: ActionCtx, request: Request)
|
||||
const skillResult = (await ctx.runQuery(api.skills.getBySlug, { slug })) as GetBySlugResult
|
||||
if (!skillResult?.skill) return text('Skill not found', 404, rate.headers)
|
||||
|
||||
let version = skillResult.latestVersion
|
||||
let version: Doc<'skillVersions'> | null = skillResult.skill.latestVersionId
|
||||
? await ctx.runQuery(internal.skills.getVersionByIdInternal, {
|
||||
versionId: skillResult.skill.latestVersionId,
|
||||
})
|
||||
: null
|
||||
if (versionParam) {
|
||||
version = await ctx.runQuery(api.skills.getVersionBySkillAndVersion, {
|
||||
version = await ctx.runQuery(internal.skills.getVersionBySkillAndVersionInternal, {
|
||||
skillId: skillResult.skill._id,
|
||||
version: versionParam,
|
||||
})
|
||||
} else if (tagParam) {
|
||||
const versionId = skillResult.skill.tags[tagParam]
|
||||
if (versionId) {
|
||||
version = await ctx.runQuery(api.skills.getVersionById, { versionId })
|
||||
version = await ctx.runQuery(internal.skills.getVersionByIdInternal, { versionId })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+124
-18
@@ -1030,18 +1030,58 @@ type PublicSkillListVersion = Pick<
|
||||
| 'changelog'
|
||||
| 'changelogSource'
|
||||
> & {
|
||||
parsed?: {
|
||||
license?: typeof PLATFORM_SKILL_LICENSE
|
||||
clawdis?: {
|
||||
os?: string[]
|
||||
nix?: {
|
||||
plugin?: boolean
|
||||
systems?: string[]
|
||||
}
|
||||
parsed?: PublicSkillVersionParsed
|
||||
}
|
||||
|
||||
type PublicSkillVersionParsed = {
|
||||
license?: typeof PLATFORM_SKILL_LICENSE
|
||||
clawdis?: {
|
||||
os?: string[]
|
||||
nix?: {
|
||||
plugin?: boolean
|
||||
systems?: string[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type PublicSkillVersion = {
|
||||
_id: Id<'skillVersions'>
|
||||
_creationTime?: number
|
||||
skillId?: Id<'skills'>
|
||||
version: string
|
||||
fingerprint?: string
|
||||
changelog?: string
|
||||
changelogSource?: Doc<'skillVersions'>['changelogSource']
|
||||
files: Array<{
|
||||
path: string
|
||||
size: number
|
||||
sha256: string
|
||||
contentType?: string
|
||||
}>
|
||||
parsed?: PublicSkillVersionParsed
|
||||
createdBy?: Id<'users'>
|
||||
createdAt?: number
|
||||
softDeletedAt?: number
|
||||
sha256hash?: string
|
||||
vtAnalysis?: Doc<'skillVersions'>['vtAnalysis']
|
||||
llmAnalysis?: Doc<'skillVersions'>['llmAnalysis']
|
||||
staticScan?: {
|
||||
status: NonNullable<Doc<'skillVersions'>['staticScan']>['status']
|
||||
reasonCodes: NonNullable<Doc<'skillVersions'>['staticScan']>['reasonCodes']
|
||||
findings: Array<{
|
||||
code: string
|
||||
severity: 'info' | 'warn' | 'critical'
|
||||
file: string
|
||||
line: number
|
||||
message: string
|
||||
evidence: string
|
||||
}>
|
||||
summary: NonNullable<Doc<'skillVersions'>['staticScan']>['summary']
|
||||
engineVersion: NonNullable<Doc<'skillVersions'>['staticScan']>['engineVersion']
|
||||
checkedAt: NonNullable<Doc<'skillVersions'>['staticScan']>['checkedAt']
|
||||
}
|
||||
}
|
||||
|
||||
type ManagementSkillEntry = {
|
||||
skill: Doc<'skills'>
|
||||
latestVersion: Doc<'skillVersions'> | null
|
||||
@@ -1176,6 +1216,56 @@ function toPublicSkillListVersion(
|
||||
}
|
||||
}
|
||||
|
||||
function toPublicSkillVersion(
|
||||
version: Doc<'skillVersions'> | null | undefined,
|
||||
): PublicSkillVersion | null {
|
||||
if (!version) return null
|
||||
return {
|
||||
_id: version._id,
|
||||
_creationTime: version._creationTime,
|
||||
skillId: version.skillId,
|
||||
version: version.version,
|
||||
fingerprint: version.fingerprint,
|
||||
changelog: version.changelog,
|
||||
changelogSource: version.changelogSource,
|
||||
files: (version.files ?? []).map((file) => ({
|
||||
path: file.path,
|
||||
size: file.size,
|
||||
sha256: file.sha256,
|
||||
contentType: file.contentType,
|
||||
})),
|
||||
parsed: version.parsed
|
||||
? {
|
||||
license: version.parsed.license,
|
||||
clawdis: version.parsed.clawdis,
|
||||
}
|
||||
: undefined,
|
||||
createdBy: version.createdBy,
|
||||
createdAt: version.createdAt,
|
||||
softDeletedAt: version.softDeletedAt,
|
||||
sha256hash: version.sha256hash,
|
||||
vtAnalysis: version.vtAnalysis,
|
||||
llmAnalysis: version.llmAnalysis,
|
||||
staticScan: version.staticScan
|
||||
? {
|
||||
status: version.staticScan.status,
|
||||
reasonCodes: version.staticScan.reasonCodes,
|
||||
findings: (version.staticScan.findings ?? []).map((finding) => ({
|
||||
code: finding.code,
|
||||
severity: finding.severity,
|
||||
file: finding.file,
|
||||
line: finding.line,
|
||||
message: finding.message,
|
||||
evidence: '',
|
||||
})),
|
||||
summary: version.staticScan.summary,
|
||||
engineVersion: version.staticScan.engineVersion,
|
||||
checkedAt: version.staticScan.checkedAt,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
function toPublicSkillListVersionFromSummary(
|
||||
summary: NonNullable<Doc<'skills'>['latestVersionSummary']>,
|
||||
latestVersionId: Id<'skillVersions'> | undefined,
|
||||
@@ -1322,9 +1412,9 @@ export const getBySlug = query({
|
||||
const userId = await getAuthUserId(ctx)
|
||||
const isOwner = Boolean(userId && userId === skill.ownerUserId)
|
||||
|
||||
const latestVersion = skill.latestVersionId
|
||||
? await ctx.db.get(skill.latestVersionId)
|
||||
: null
|
||||
const latestVersion = toPublicSkillVersion(
|
||||
skill.latestVersionId ? await ctx.db.get(skill.latestVersionId) : null,
|
||||
)
|
||||
const owner = toPublicUser(await ctx.db.get(skill.ownerUserId))
|
||||
if (!owner) return null
|
||||
const badges = await getSkillBadgeMap(ctx, skill._id)
|
||||
@@ -2133,9 +2223,9 @@ export const listWithLatest = query({
|
||||
const items = await Promise.all(
|
||||
limited.map(async (skill) => ({
|
||||
skill: toPublicSkill(skill),
|
||||
latestVersion: skill.latestVersionId
|
||||
? await ctx.db.get(skill.latestVersionId)
|
||||
: null,
|
||||
latestVersion: toPublicSkillVersion(
|
||||
skill.latestVersionId ? await ctx.db.get(skill.latestVersionId) : null,
|
||||
),
|
||||
})),
|
||||
)
|
||||
return items.filter(
|
||||
@@ -2688,11 +2778,12 @@ export const listVersions = query({
|
||||
args: { skillId: v.id('skills'), limit: v.optional(v.number()) },
|
||||
handler: async (ctx, args) => {
|
||||
const limit = args.limit ?? 20
|
||||
return ctx.db
|
||||
const versions = await ctx.db
|
||||
.query('skillVersions')
|
||||
.withIndex('by_skill', (q) => q.eq('skillId', args.skillId))
|
||||
.order('desc')
|
||||
.take(limit)
|
||||
return versions.map((version) => toPublicSkillVersion(version)!)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -2709,14 +2800,16 @@ export const listVersionsPage = query({
|
||||
.withIndex('by_skill', (q) => q.eq('skillId', args.skillId))
|
||||
.order('desc')
|
||||
.paginate({ cursor: args.cursor ?? null, numItems: limit })
|
||||
const items = page.filter((version) => !version.softDeletedAt)
|
||||
const items = page
|
||||
.filter((version) => !version.softDeletedAt)
|
||||
.map((version) => toPublicSkillVersion(version)!)
|
||||
return { items, nextCursor: isDone ? null : continueCursor }
|
||||
},
|
||||
})
|
||||
|
||||
export const getVersionById = query({
|
||||
args: { versionId: v.id('skillVersions') },
|
||||
handler: async (ctx, args) => ctx.db.get(args.versionId),
|
||||
handler: async (ctx, args) => toPublicSkillVersion(await ctx.db.get(args.versionId)),
|
||||
})
|
||||
|
||||
export const getVersionsByIdsInternal = internalQuery({
|
||||
@@ -2734,6 +2827,18 @@ export const getVersionByIdInternal = internalQuery({
|
||||
handler: async (ctx, args) => ctx.db.get(args.versionId),
|
||||
})
|
||||
|
||||
export const getVersionBySkillAndVersionInternal = internalQuery({
|
||||
args: { skillId: v.id('skills'), version: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
return ctx.db
|
||||
.query('skillVersions')
|
||||
.withIndex('by_skill_version', (q) =>
|
||||
q.eq('skillId', args.skillId).eq('version', args.version),
|
||||
)
|
||||
.unique()
|
||||
},
|
||||
})
|
||||
|
||||
export const getSkillByIdInternal = internalQuery({
|
||||
args: { skillId: v.id('skills') },
|
||||
handler: async (ctx, args) => ctx.db.get(args.skillId),
|
||||
@@ -4086,12 +4191,13 @@ export const escalateByVtInternal = internalMutation({
|
||||
export const getVersionBySkillAndVersion = query({
|
||||
args: { skillId: v.id('skills'), version: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
return ctx.db
|
||||
const version = await ctx.db
|
||||
.query('skillVersions')
|
||||
.withIndex('by_skill_version', (q) =>
|
||||
q.eq('skillId', args.skillId).eq('version', args.version),
|
||||
)
|
||||
.unique()
|
||||
return toPublicSkillVersion(version)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
/* @vitest-environment node */
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('@convex-dev/auth/server', () => ({
|
||||
getAuthUserId: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('./lib/badges', () => ({
|
||||
getSkillBadgeMap: vi.fn(),
|
||||
getSkillBadgeMaps: vi.fn(),
|
||||
isSkillHighlighted: vi.fn(),
|
||||
}))
|
||||
|
||||
const { getAuthUserId } = await import('@convex-dev/auth/server')
|
||||
const { getSkillBadgeMap, getSkillBadgeMaps } = await import('./lib/badges')
|
||||
const { getBySlug, getVersionById, getVersionBySkillAndVersion, listVersions, listWithLatest } =
|
||||
await import('./skills')
|
||||
|
||||
type WrappedHandler<TArgs, TResult = unknown> = {
|
||||
_handler: (ctx: unknown, args: TArgs) => Promise<TResult>
|
||||
}
|
||||
|
||||
const getBySlugHandler = (
|
||||
getBySlug as unknown as WrappedHandler<{
|
||||
slug: string
|
||||
}>
|
||||
)._handler
|
||||
|
||||
const getVersionByIdHandler = (
|
||||
getVersionById as unknown as WrappedHandler<{
|
||||
versionId: string
|
||||
}>
|
||||
)._handler
|
||||
|
||||
const getVersionBySkillAndVersionHandler = (
|
||||
getVersionBySkillAndVersion as unknown as WrappedHandler<{
|
||||
skillId: string
|
||||
version: string
|
||||
}>
|
||||
)._handler
|
||||
|
||||
const listVersionsHandler = (
|
||||
listVersions as unknown as WrappedHandler<{
|
||||
skillId: string
|
||||
limit?: number
|
||||
}>
|
||||
)._handler
|
||||
|
||||
const listWithLatestHandler = (
|
||||
listWithLatest as unknown as WrappedHandler<{
|
||||
limit?: number
|
||||
}>
|
||||
)._handler
|
||||
|
||||
function makeVersion() {
|
||||
return {
|
||||
_id: 'skillVersions:1',
|
||||
_creationTime: 1,
|
||||
skillId: 'skills:1',
|
||||
version: '1.0.0',
|
||||
fingerprint: 'fp',
|
||||
changelog: 'Initial release',
|
||||
changelogSource: 'auto',
|
||||
files: [
|
||||
{
|
||||
path: 'SKILL.md',
|
||||
size: 10,
|
||||
storageId: '_storage:1',
|
||||
sha256: 'abc123',
|
||||
contentType: 'text/markdown',
|
||||
},
|
||||
],
|
||||
parsed: {
|
||||
frontmatter: { secret: 'value' },
|
||||
metadata: { hidden: true },
|
||||
clawdis: { os: ['macos'] },
|
||||
moltbot: { prompt: 'hidden' },
|
||||
license: 'MIT-0',
|
||||
},
|
||||
createdBy: 'users:1',
|
||||
createdAt: 100,
|
||||
softDeletedAt: undefined,
|
||||
sha256hash: 'deadbeef',
|
||||
vtAnalysis: {
|
||||
status: 'clean',
|
||||
verdict: 'clean',
|
||||
analysis: 'safe',
|
||||
source: 'code_insight',
|
||||
checkedAt: 1,
|
||||
},
|
||||
llmAnalysis: {
|
||||
status: 'clean',
|
||||
verdict: 'benign',
|
||||
confidence: 'high',
|
||||
summary: 'Looks safe',
|
||||
dimensions: [],
|
||||
guidance: 'ok',
|
||||
findings: 'none',
|
||||
model: 'gpt',
|
||||
checkedAt: 1,
|
||||
},
|
||||
staticScan: {
|
||||
status: 'suspicious',
|
||||
reasonCodes: ['scanner.example'],
|
||||
findings: [
|
||||
{
|
||||
code: 'scanner.example',
|
||||
severity: 'warn',
|
||||
file: 'SKILL.md',
|
||||
line: 1,
|
||||
message: 'Example finding',
|
||||
evidence: 'SECRET_SNIPPET',
|
||||
},
|
||||
],
|
||||
summary: 'Something matched',
|
||||
engineVersion: '1',
|
||||
checkedAt: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe('public skill version queries', () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(getAuthUserId).mockReset()
|
||||
vi.mocked(getSkillBadgeMap).mockReset()
|
||||
vi.mocked(getSkillBadgeMaps).mockReset()
|
||||
vi.mocked(getAuthUserId).mockResolvedValue(null as never)
|
||||
vi.mocked(getSkillBadgeMap).mockResolvedValue({} as never)
|
||||
vi.mocked(getSkillBadgeMaps).mockResolvedValue(new Map() as never)
|
||||
})
|
||||
|
||||
it('sanitizes latestVersion returned by getBySlug', async () => {
|
||||
const version = makeVersion()
|
||||
const ctx = {
|
||||
db: {
|
||||
query: vi.fn((table: string) => {
|
||||
if (table !== 'skills') throw new Error(`Unexpected table ${table}`)
|
||||
return {
|
||||
withIndex: vi.fn(() => ({
|
||||
unique: vi.fn().mockResolvedValue({
|
||||
_id: 'skills:1',
|
||||
_creationTime: 1,
|
||||
slug: 'demo',
|
||||
displayName: 'Demo',
|
||||
summary: 'Summary',
|
||||
ownerUserId: 'users:1',
|
||||
canonicalSkillId: undefined,
|
||||
forkOf: undefined,
|
||||
latestVersionId: version._id,
|
||||
tags: {},
|
||||
stats: {
|
||||
downloads: 1,
|
||||
installsCurrent: 1,
|
||||
installsAllTime: 1,
|
||||
stars: 1,
|
||||
versions: 1,
|
||||
comments: 0,
|
||||
},
|
||||
createdAt: 1,
|
||||
updatedAt: 2,
|
||||
moderationStatus: 'active',
|
||||
moderationFlags: undefined,
|
||||
moderationReason: undefined,
|
||||
softDeletedAt: undefined,
|
||||
}),
|
||||
})),
|
||||
}
|
||||
}),
|
||||
get: vi.fn(async (id: string) => {
|
||||
if (id === version._id) return version
|
||||
if (id === 'users:1') {
|
||||
return {
|
||||
_id: 'users:1',
|
||||
_creationTime: 1,
|
||||
handle: 'demo',
|
||||
name: 'demo',
|
||||
displayName: 'Demo',
|
||||
image: null,
|
||||
bio: null,
|
||||
}
|
||||
}
|
||||
return null
|
||||
}),
|
||||
},
|
||||
} as never
|
||||
|
||||
const result = (await getBySlugHandler(ctx, { slug: 'demo' } as never)) as {
|
||||
latestVersion?: {
|
||||
files: Array<Record<string, unknown>>
|
||||
parsed?: Record<string, unknown>
|
||||
staticScan?: { findings?: Array<{ evidence?: string }> }
|
||||
} | null
|
||||
} | null
|
||||
|
||||
expect(result?.latestVersion?.files[0]).not.toHaveProperty('storageId')
|
||||
expect(result?.latestVersion?.parsed).toEqual({
|
||||
clawdis: { os: ['macos'] },
|
||||
license: 'MIT-0',
|
||||
})
|
||||
expect(result?.latestVersion?.staticScan?.findings?.[0]?.evidence).toBe('')
|
||||
})
|
||||
|
||||
it('sanitizes direct public version queries', async () => {
|
||||
const version = makeVersion()
|
||||
const unique = vi.fn().mockResolvedValue(version)
|
||||
const take = vi.fn().mockResolvedValue([version])
|
||||
const ctx = {
|
||||
db: {
|
||||
get: vi.fn().mockResolvedValue(version),
|
||||
query: vi.fn((table: string) => {
|
||||
if (table !== 'skillVersions') throw new Error(`Unexpected table ${table}`)
|
||||
return {
|
||||
withIndex: vi.fn(() => ({
|
||||
unique,
|
||||
order: vi.fn(() => ({ take })),
|
||||
})),
|
||||
}
|
||||
}),
|
||||
},
|
||||
} as never
|
||||
|
||||
const byId = (await getVersionByIdHandler(ctx, {
|
||||
versionId: version._id,
|
||||
} as never)) as
|
||||
| {
|
||||
files: Array<Record<string, unknown>>
|
||||
parsed?: Record<string, unknown>
|
||||
staticScan?: { findings?: Array<{ evidence?: string }> }
|
||||
}
|
||||
| null
|
||||
const byVersion = (await getVersionBySkillAndVersionHandler(
|
||||
ctx,
|
||||
{ skillId: 'skills:1', version: '1.0.0' } as never,
|
||||
)) as
|
||||
| {
|
||||
files: Array<Record<string, unknown>>
|
||||
parsed?: Record<string, unknown>
|
||||
staticScan?: { findings?: Array<{ evidence?: string }> }
|
||||
}
|
||||
| null
|
||||
const list = (await listVersionsHandler(ctx, {
|
||||
skillId: 'skills:1',
|
||||
limit: 5,
|
||||
} as never)) as Array<{
|
||||
files: Array<Record<string, unknown>>
|
||||
parsed?: Record<string, unknown>
|
||||
staticScan?: { findings?: Array<{ evidence?: string }> }
|
||||
}>
|
||||
|
||||
for (const result of [byId, byVersion, list[0]]) {
|
||||
expect(result?.files[0]).not.toHaveProperty('storageId')
|
||||
expect(result?.parsed).not.toHaveProperty('frontmatter')
|
||||
expect(result?.parsed).not.toHaveProperty('metadata')
|
||||
expect(result?.parsed).not.toHaveProperty('moltbot')
|
||||
expect(result?.staticScan?.findings?.[0]?.evidence).toBe('')
|
||||
}
|
||||
})
|
||||
|
||||
it('sanitizes latestVersion in listWithLatest', async () => {
|
||||
const version = makeVersion()
|
||||
const ctx = {
|
||||
db: {
|
||||
query: vi.fn((table: string) => {
|
||||
if (table !== 'skills') throw new Error(`Unexpected table ${table}`)
|
||||
return {
|
||||
order: vi.fn(() => ({
|
||||
take: vi.fn().mockResolvedValue([
|
||||
{
|
||||
_id: 'skills:1',
|
||||
_creationTime: 1,
|
||||
slug: 'demo',
|
||||
displayName: 'Demo',
|
||||
summary: 'Summary',
|
||||
ownerUserId: 'users:1',
|
||||
canonicalSkillId: undefined,
|
||||
forkOf: undefined,
|
||||
latestVersionId: version._id,
|
||||
tags: {},
|
||||
badges: undefined,
|
||||
stats: {
|
||||
downloads: 1,
|
||||
installsCurrent: 1,
|
||||
installsAllTime: 1,
|
||||
stars: 1,
|
||||
versions: 1,
|
||||
comments: 0,
|
||||
},
|
||||
createdAt: 1,
|
||||
updatedAt: 2,
|
||||
softDeletedAt: undefined,
|
||||
moderationStatus: 'active',
|
||||
moderationFlags: undefined,
|
||||
moderationReason: undefined,
|
||||
},
|
||||
]),
|
||||
})),
|
||||
}
|
||||
}),
|
||||
get: vi.fn().mockResolvedValue(version),
|
||||
},
|
||||
} as never
|
||||
|
||||
const result = (await listWithLatestHandler(ctx, { limit: 1 } as never)) as Array<{
|
||||
latestVersion?: {
|
||||
files: Array<Record<string, unknown>>
|
||||
parsed?: Record<string, unknown>
|
||||
} | null
|
||||
}>
|
||||
expect(result[0]?.latestVersion?.files[0]).not.toHaveProperty('storageId')
|
||||
expect(result[0]?.latestVersion?.parsed).not.toHaveProperty('frontmatter')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user