Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
2 changes: 1 addition & 1 deletion .github/release-please/manifest.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{".":"0.4.0"}
{".":"0.4.1"}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [0.4.1](https://github.com/do-pa/itdoc/compare/v0.4.0...v0.4.1) (2025-08-21)


### 🩹 Fixes

* og_image is not working ([#231](https://github.com/do-pa/itdoc/issues/231)) ([d1070ec](https://github.com/do-pa/itdoc/commit/d1070ec1064cf914b576e2eb9153378903ccaf89)), closes [#230](https://github.com/do-pa/itdoc/issues/230)
* resolve issue causing LLM script to not run properly ([#235](https://github.com/do-pa/itdoc/issues/235)) ([1fa8d90](https://github.com/do-pa/itdoc/commit/1fa8d90fe14c37031baaa58f375550785f055275))

## [0.4.0](https://github.com/do-pa/itdoc/compare/v0.3.0...v0.4.0) (2025-08-08)


Expand Down
21 changes: 10 additions & 11 deletions bin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,19 @@ Example:
program
.command("generate")
.description("Generate ITDOC test code based on LLM.")
.option("-p, --path <testspecPath>", "Path to the markdown test spec file.")
.option("-a, --app <appPath>", "Path to the Express root app file.")
.option("-e, --env <envPath>", "Path to the .env file.")
.action((options: { path?: string; env?: string; app?: string }) => {
.action(async (options: { env?: string; app?: string }) => {
const envPath = options.env
? path.isAbsolute(options.env)
? options.env
: path.resolve(process.cwd(), options.env)
: path.resolve(process.cwd(), ".env")

if (!options.path && !options.app) {
if (!options.app) {
logger.error(
"Either a test spec path (-p) or an Express app path (-a) must be provided. By default, the OpenAI key (OPENAI_API_KEY in .env) is loaded from the root directory, but you can customize the path if needed",
"An Express app path (-a) must be provided. By default, the OpenAI key (OPENAI_API_KEY in .env) is loaded from the root directory, but you can customize the path with -e/--env if needed.",
)
logger.info("ex) itdoc generate -p ../md/testspec.md")
logger.info("ex) itdoc generate --path ../md/testspec.md")
logger.info("ex) itdoc generate -a ../app.js")
logger.info("ex) itdoc generate --app ../app.js")
logger.info("ex) itdoc generate -a ../app.js -e <custom path : env>")
Expand All @@ -80,12 +77,14 @@ program
logger.box("ITDOC LLM START")
if (options.app) {
const appPath = resolvePath(options.app)

logger.info(`Running analysis based on Express app path: ${appPath}`)
generateByLLM("", appPath, envPath)
} else if (options.path) {
const specPath = resolvePath(options.path)
logger.info(`Running analysis based on test spec (MD) path: ${specPath}`)
generateByLLM(specPath, "", envPath)
try {
await generateByLLM(appPath, envPath)
} catch (err) {
logger.error(`LLM generation failed: ${(err as Error).message}`)
process.exit(1)
}
}
})

Expand Down
1 change: 1 addition & 0 deletions examples/express-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@types/express": "^4.17.21",
"@types/jest": "^30.0.0",
"@types/mocha": "^10.0.10",
"@types/multer": "^2.0.0",
"@types/node": "^20.10.5",
"@types/supertest": "^6.0.2",
"itdoc": "workspace:*",
Expand Down
211 changes: 211 additions & 0 deletions examples/express-ts/src/__tests__/product.wrapper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/**
* Product API Tests using wrapTest wrapper approach
*
* This demonstrates the new high-order function wrapping method
* that automatically captures HTTP requests/responses
*/

import { app } from "../index"
import { wrapTest, request } from "itdoc"

// Create wrapped test function
const apiTest = wrapTest(it)

describe("Product API - Wrapper Approach", () => {
describe("GET /api/products/:id", () => {
apiTest.withMeta({
summary: "Get product by ID",
tags: ["Products"],
description: "Retrieves a specific product by its ID",
})("should return a specific product", async () => {
const response = await request(app).get("/api/products/1")

expect(response.status).toBe(200)
expect(response.body).toHaveProperty("id", 1)
expect(response.body).toHaveProperty("name", "Laptop")
expect(response.body).toHaveProperty("price", 999.99)
expect(response.body).toHaveProperty("category", "Electronics")
})

apiTest("should return product with different ID", async () => {
const response = await request(app).get("/api/products/2")

expect(response.status).toBe(200)
expect(response.body).toHaveProperty("id", 2)
expect(response.body).toHaveProperty("name", "Phone")
})
})

describe("POST /api/products", () => {
apiTest.withMeta({
summary: "Create new product",
tags: ["Products", "Create"],
description: "Creates a new product with the provided information",
})("should create a new product", async () => {
const response = await request(app).post("/api/products").send({
name: "Test Product",
price: 99.99,
category: "Test Category",
})

expect(response.status).toBe(201)
expect(response.body).toHaveProperty("id", 3)
expect(response.body).toHaveProperty("name", "Test Product")
expect(response.body).toHaveProperty("price", 99.99)
expect(response.body).toHaveProperty("category", "Test Category")
})

apiTest.withMeta({
summary: "Create product with different data",
tags: ["Products", "Create"],
})("should create another product", async () => {
const response = await request(app).post("/api/products").send({
name: "Another Product",
price: 199.99,
category: "Another Category",
})

expect(response.status).toBe(201)
expect(response.body.name).toBe("Another Product")
})
})

describe("PUT /api/products/:id", () => {
apiTest.withMeta({
summary: "Update product",
tags: ["Products", "Update"],
description: "Updates an existing product with the provided information",
})("should update a product", async () => {
const response = await request(app).put("/api/products/1").send({
name: "Updated Product",
price: 199.99,
category: "Updated Category",
})

expect(response.status).toBe(200)
expect(response.body).toHaveProperty("id", 1)
expect(response.body).toHaveProperty("name", "Updated Product")
expect(response.body).toHaveProperty("price", 199.99)
expect(response.body).toHaveProperty("category", "Updated Category")
})

apiTest("should update product with partial data", async () => {
const response = await request(app).put("/api/products/2").send({
name: "Partially Updated",
price: 299.99,
category: "Electronics",
})

expect(response.status).toBe(200)
expect(response.body.name).toBe("Partially Updated")
})
})

describe("DELETE /api/products/:id", () => {
apiTest.withMeta({
summary: "Delete product",
tags: ["Products", "Delete"],
description: "Deletes a product by its ID",
})("should delete a product", async () => {
const response = await request(app).delete("/api/products/1")

expect(response.status).toBe(204)
})

apiTest("should delete another product", async () => {
const response = await request(app).delete("/api/products/2")

expect(response.status).toBe(204)
})
})
Comment thread
json-choi marked this conversation as resolved.
Outdated

describe("Complete product CRUD workflow", () => {
apiTest.withMeta({
summary: "Product CRUD workflow",
tags: ["Products", "Workflow", "CRUD"],
description: "Complete create, read, update, delete workflow for products",
})("should perform complete CRUD operations", async () => {
// Step 1: Create a product
const createResponse = await request(app).post("/api/products").send({
name: "Workflow Product",
price: 149.99,
category: "Test",
})

expect(createResponse.status).toBe(201)
const productId = createResponse.body.id

// Step 2: Read the product
const getResponse = await request(app).get(`/api/products/${productId}`)

expect(getResponse.status).toBe(200)
expect(getResponse.body.name).toBe("Workflow Product")

// Step 3: Update the product
const updateResponse = await request(app).put(`/api/products/${productId}`).send({
name: "Updated Workflow Product",
price: 179.99,
category: "Updated Test",
})

expect(updateResponse.status).toBe(200)
expect(updateResponse.body.name).toBe("Updated Workflow Product")

// Step 4: Delete the product
const deleteResponse = await request(app).delete(`/api/products/${productId}`)

expect(deleteResponse.status).toBe(204)
})
})

describe("Product filtering and search", () => {
apiTest.withMeta({
summary: "Filter products by category",
tags: ["Products", "Filter"],
})("should filter products with query params", async () => {
const response = await request(app)
.get("/api/products/1")
.query({ category: "Electronics", minPrice: 500 })

expect(response.status).toBe(200)
})

apiTest("should search products with multiple params", async () => {
const response = await request(app).get("/api/products/1").query({
search: "laptop",
sortBy: "price",
order: "asc",
})

expect(response.status).toBe(200)
})
Comment thread
json-choi marked this conversation as resolved.
Outdated
})

describe("Product API with authentication", () => {
apiTest.withMeta({
summary: "Create product with auth",
tags: ["Products", "Authentication"],
})("should create product with authorization header", async () => {
const response = await request(app)
.post("/api/products")
.set("Authorization", "Bearer fake-token-123")
.send({
name: "Authenticated Product",
price: 299.99,
category: "Secure",
})

expect(response.status).toBe(201)
})

apiTest("should include custom headers", async () => {
const response = await request(app)
.get("/api/products/1")
.set("Authorization", "Bearer token")
.set("X-Client-ID", "test-client")
.set("Accept", "application/json")

expect(response.status).toBe(200)
})
})
})
Loading
Loading