원클릭으로
drax-crud-test-services
Guide for generating comprehensive service layer tests for Drax CRUD modules using Vitest
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Guide for generating comprehensive service layer tests for Drax CRUD modules using Vitest
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Use this skill when working with the Drax Vue CRUD composable useCrud, its Pinia store useCrudStore, or CRUD UI components that depend on them. It explains the public API, state, filters, pagination, navigation, import/export, validation errors, and provider integration patterns in packages/crud/crud-vue/src/composables/UseCrud.ts and stores/UseCrudStore.ts.
Explain and apply the Drax media backend module for file management. Use when a project needs to upload, download, delete, register, query, or delegate file handling to @drax/media-back, especially MediaService, FileService, FileServiceFactory, MediaRoutes, FileRoutes, MediaPermissions, or FilePermissions.
Usa esta skill cuando necesites personalizar o extender de forma centralizada los botones reutilizables de @drax/crud-vue, especialmente iconos, variant, rounded y color de los componentes en packages/crud/crud-vue/src/components/buttons.
Explica como funciona y como usar BuildContextTool en Drax AI para construir tools CRUD con contexto de usuario/tenant, filtros automaticos, setters de ownership, asserts de lectura/escritura y permisos All/ViewAll/UpdateAll/DeleteAll. Usar cuando el usuario pida documentacion, ejemplos o implementaciones de BuildContextTool, BuilderTool con contexto, tools AI para entidades, fromPromptContext, tenantFilter, userFilter, tenantSetter, userSetter, tenantAssert o userAssert.
Explica como usar AiProviderFactory e IAIProvider en Drax para ejecutar prompts completos de forma agnostica al proveedor, incluyendo systemPrompt, userInput, userContent, userImages, history, memory, knowledgeBase, tools, toolMaxIterations, jsonSchema, zodSchema, model y metadatos de operacion. Usar cuando el usuario pida ejemplos, documentacion o implementaciones basadas en AiProviderFactory, IAIProvider, tools de AI, AIGenericController, AICrudController o AiTestController.
Guia para crear o modificar entidades CRUD backend en Drax usando AbstractService, AbstractFastifyController, filtros IDraxFieldFilter, repositorios, schemas, permissions, factories y routes. Usar cuando el usuario pida generar CRUDs, agregar endpoints, acciones, filtros, imports, exports, groupBy, hooks, service methods o controllers backend; priorizar siempre los metodos existentes de AbstractService y AbstractFastifyController antes de crear metodos nuevos.
| name | drax-crud-test-services |
| description | Guide for generating comprehensive service layer tests for Drax CRUD modules using Vitest |
This skill provides comprehensive guidance on generating service layer tests for Drax CRUD modules using Vitest. Service tests verify business logic by testing service methods directly, without the HTTP layer.
For each CRUD entity in Drax, create a service test file:
File location: back/test/modules/{module-name}/{entity}/{entity}-service.test.ts
Purpose: Test service layer methods directly (business logic, database operations)
All service tests use the TestSetup class which provides:
dropCollection, dropAndClose)rootUser, basicUser) for testing user-related operationsimport { describe, it, beforeAll, afterAll, expect } from "vitest"
import EntityServiceFactory from "../../../../src/modules/{module}/factory/services/EntityServiceFactory"
import EntityService from "../../../../src/modules/{module}/services/EntityService"
import TestSetup from "../../../setup/TestSetup"
import type { IEntityBase } from "../../../../src/modules/{module}/interfaces/IEntity"
describe("Entity Service Test", function () {
let testSetup = new TestSetup()
let entityService: EntityService
beforeAll(async () => {
await testSetup.setup()
entityService = EntityServiceFactory.instance
})
afterAll(async () => {
await testSetup.dropAndClose()
return
})
// Test cases here
})
Tests create() and findById() methods
it("should create a new {entity} and find by id", async () => {
await testSetup.dropCollection('Entity')
const newEntity: IEntityBase = {
name: "Test Entity",
description: "Test description"
}
const entity = await entityService.create(newEntity)
expect(entity.name).toBe("Test Entity")
expect(entity._id).toBeDefined()
const getEntity = await entityService.findById(entity._id)
expect(getEntity?.name).toBe("Test Entity")
})
Tests update() method for full updates
it("should create and update a {entity} and finally find by id", async () => {
await testSetup.dropCollection('Entity')
const newEntity: IEntityBase = {
name: "Test Entity",
description: "Original description"
}
const entity = await entityService.create(newEntity)
const updateData: IEntityBase = {
name: "Updated Entity",
description: "Updated description"
}
const updatedEntity = await entityService.update(entity._id, updateData)
expect(updatedEntity.name).toBe("Updated Entity")
expect(updatedEntity.description).toBe("Updated description")
const getEntity = await entityService.findById(entity._id)
expect(getEntity?.name).toBe("Updated Entity")
})
Tests delete() method
it("should create and delete a {entity}", async () => {
await testSetup.dropCollection('Entity')
const newEntity: IEntityBase = {
name: "Test Entity",
description: "Test description"
}
const entity = await entityService.create(newEntity)
expect(entity._id).toBeDefined()
const deleteResult = await entityService.delete(entity._id)
expect(deleteResult).toBe(true)
const getEntity = await entityService.findById(entity._id)
expect(getEntity).toBe(null)
})
Tests paginate() method
it("Should create and paginate {entities}", async () => {
await testSetup.dropCollection('Entity')
const entityData: IEntityBase[] = [
{ name: "Entity 1", description: "Description 1" },
{ name: "Entity 2", description: "Description 2" }
]
for (const data of entityData) {
await entityService.create(data)
}
const result = await entityService.paginate({ page: 1, limit: 10 })
expect(result.items.length).toBe(2)
expect(result.total).toBe(2)
expect(result.page).toBe(1)
expect(result.limit).toBe(10)
expect(result.items[0].name).toBe("Entity 1")
})
Tests search() method
it("should search for {entities}", async () => {
await testSetup.dropCollection('Entity')
const entityData: IEntityBase[] = [
{ name: "SearchEntity1", description: "Description 1" },
{ name: "SearchEntity2", description: "Description 2" },
{ name: "OtherEntity", description: "Description 3" }
]
for (const data of entityData) {
await entityService.create(data)
}
const searchResult = await entityService.search("Search")
expect(searchResult.length).toBe(2)
expect(searchResult.some(e => e.name === "SearchEntity1")).toBe(true)
expect(searchResult.some(e => e.name === "SearchEntity2")).toBe(true)
expect(searchResult.some(e => e.name === "OtherEntity")).toBe(false)
})
Tests find() method with filter criteria
it("should find {entities} with filters", async () => {
await testSetup.dropCollection('Entity')
const entityData: IEntityBase[] = [
{ name: "FilterEntityA", status: "active" },
{ name: "FilterEntityB", status: "inactive" }
]
for (const data of entityData) {
await entityService.create(data)
}
const findByResult = await entityService.find({
filters: [
{ field: "status", operator: "eq", value: "active" }
]
})
expect(Array.isArray(findByResult)).toBe(true)
expect(findByResult.length).toBe(1)
expect(findByResult[0].name).toBe("FilterEntityA")
})
Tests error handling when entity doesn't exist
it("should handle error responses correctly when {entity} is not found", async () => {
const nonExistentId = "123456789012345678901234"
const resp = await entityService.findById(nonExistentId)
expect(resp).toBe(null)
})
import { describe, it, beforeAll, afterAll, expect } from "vitest"
import NotificationServiceFactory from "../../../../src/modules/base/factory/services/NotificationServiceFactory"
import NotificationService from "../../../../src/modules/base/services/NotificationService"
import TestSetup from "../../../setup/TestSetup"
import type { INotificationBase } from "../../../../src/modules/base/interfaces/INotification"
describe("Notification Service Test", function () {
let testSetup = new TestSetup()
let notificationService: NotificationService
beforeAll(async () => {
await testSetup.setup()
notificationService = NotificationServiceFactory.instance
})
afterAll(async () => {
await testSetup.dropAndClose()
return
})
it("should create a new notification and find by id", async () => {
await testSetup.dropCollection('Notification')
const newNotification: INotificationBase = {
title: "Test Notification",
message: "This is a test notification message",
type: "info",
status: "unread",
user: testSetup.rootUser._id
}
const notification = await notificationService.create(newNotification)
expect(notification.title).toBe("Test Notification")
expect(notification._id).toBeDefined()
const getNotification = await notificationService.findById(notification._id)
expect(getNotification?.title).toBe("Test Notification")
})
it("should create and update a notification and finally find by id", async () => {
await testSetup.dropCollection('Notification')
const newNotification: INotificationBase = {
title: "Test Notification",
message: "This is a test notification message",
type: "info",
status: "unread",
user: testSetup.rootUser._id
}
const notification = await notificationService.create(newNotification)
const updateData: INotificationBase = {
title: "Updated Notification",
message: "Updated message",
type: "warning",
status: "read",
user: testSetup.rootUser._id
}
const updatedNotification = await notificationService.update(notification._id, updateData)
expect(updatedNotification.title).toBe("Updated Notification")
expect(updatedNotification.type).toBe("warning")
const getNotification = await notificationService.findById(notification._id)
expect(getNotification?.title).toBe("Updated Notification")
expect(getNotification?.status).toBe("read")
})
// Additional test cases following the patterns above...
})
await testSetup.dropCollection('EntityName')beforeAll for setup and afterAll for cleanupEntityServiceFactory.instancebeforeAll hook// Create
const entity = await entityService.create(data)
// Read
const entity = await entityService.findById(id)
const entities = await entityService.paginate({ page: 1, limit: 10 })
const results = await entityService.search("term")
const filtered = await entityService.find({ filters: [...] })
// Update
const updated = await entityService.update(id, data)
// Delete
const deleted = await entityService.delete(id)
entity?.nameEnsure you cover all 7 essential service tests:
{entity}-service.test.ts"{Entity} Service Test""should [action] [expected result]"// Use pre-configured users for user-related fields
const newEntity = {
name: "Test",
user: testSetup.rootUser._id
}
// Or use basicUser for restricted scenarios
const newEntity = {
name: "Test",
user: testSetup.basicUser._id
}
# Run all tests
npm test
# Run specific test file
npm test notification-service.test.ts
# Run tests in watch mode
npm test -- --watch
# Run with coverage
npm test -- --coverage
| Aspect | Service Tests | Endpoint Tests |
|---|---|---|
| Layer | Business logic | HTTP API |
| Method | Direct method calls | HTTP requests via inject() |
| Auth | No authentication needed | Requires JWT tokens |
| Setup | Minimal (TestSetup()) | Requires routes & permissions |
| Speed | Faster | Slower (HTTP overhead) |
| Focus | Data operations | Request/response handling |
When generating service tests for a new Drax CRUD entity:
back/test/modules/{module}/{entity}/{entity}-service.test.tsbeforeAlldescribe, it, beforeAll, afterAll, expectThis ensures comprehensive test coverage for the business logic layer of your Drax CRUD modules.