feature/api-development #1

Merged
usulpro merged 47 commits from feature/api-development into main 2025-11-29 23:03:01 +07:00
6 changed files with 1925 additions and 0 deletions
Showing only changes of commit df3737ed44 - Show all commits

296
tests/api/03-flows.rest Normal file
View File

@ -0,0 +1,296 @@
@base = http://localhost:3000
@apiKey = bnt_727d2f4f72bd03ed96da5278bb971a00cb0a2454d4d70f9748b5c39f3f69d88d
###############################################################################
# FLOW LIFECYCLE TESTS
# Tests: Lazy flow creation, Eager flow creation, Flow operations
#
# Test Coverage:
# 1. Lazy flow pattern - first generation without flowId
# 2. Lazy flow - verify flow not created yet
# 3. Lazy flow - second generation creates flow
# 4. Eager flow creation with flowAlias
# 5. List all flows
# 6. Get flow with computed counts
# 7. List flow generations
# 8. List flow images
# 9. Update flow aliases
# 10. Remove specific flow alias
# 11. Regenerate flow
###############################################################################
###############################################################################
# TEST 1: Lazy Flow Pattern - First Generation
# Generation without flowId should return auto-generated flowId
# but NOT create flow in database yet (Section 4.1)
###############################################################################
### Step 1.1: Create Generation without flowId
# @name lazyFlowGen1
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "A red sports car on a mountain road",
"aspectRatio": "16:9"
}
###
@lazyFlowId = {{lazyFlowGen1.response.body.$.data.flowId}}
@lazyGenId1 = {{lazyFlowGen1.response.body.$.data.id}}
### Step 1.2: Poll Generation Status
# @name checkLazyGen1
GET {{base}}/api/v1/generations/{{lazyGenId1}}
X-API-Key: {{apiKey}}
###
# Verify:
# - flowId is returned (auto-generated UUID)
# - status = "success"
###############################################################################
# TEST 2: Verify Lazy Flow Not Created Yet
# Flow should NOT exist in database after first generation
###############################################################################
### Step 2.1: Try to get flow (should return 404)
# @name checkLazyFlowNotExists
GET {{base}}/api/v1/flows/{{lazyFlowId}}
X-API-Key: {{apiKey}}
###
# Expected: 404 Not Found
# Flow record not created yet (lazy creation pattern)
###############################################################################
# TEST 3: Lazy Flow - Second Generation Creates Flow
# Using same flowId should create the flow record
###############################################################################
### Step 3.1: Create second generation with same flowId
# @name lazyFlowGen2
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Same car but blue color",
"aspectRatio": "16:9",
"flowId": "{{lazyFlowId}}"
}
###
@lazyGenId2 = {{lazyFlowGen2.response.body.$.data.id}}
### Step 3.2: Poll Generation Status
# @name checkLazyGen2
GET {{base}}/api/v1/generations/{{lazyGenId2}}
X-API-Key: {{apiKey}}
###
### Step 3.3: Verify flow now exists
# @name verifyLazyFlowExists
GET {{base}}/api/v1/flows/{{lazyFlowId}}
X-API-Key: {{apiKey}}
###
# Expected: 200 OK
# Flow record now exists after second use
###############################################################################
# TEST 4: Eager Flow Creation with flowAlias
# Using flowAlias should create flow immediately
###############################################################################
### Step 4.1: Create generation with flowAlias
# @name eagerFlowGen
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "A hero banner image",
"aspectRatio": "21:9",
"flowAlias": "@hero-flow"
}
###
@eagerFlowId = {{eagerFlowGen.response.body.$.data.flowId}}
@eagerGenId = {{eagerFlowGen.response.body.$.data.id}}
### Step 4.2: Poll Generation Status
# @name checkEagerGen
GET {{base}}/api/v1/generations/{{eagerGenId}}
X-API-Key: {{apiKey}}
###
### Step 4.3: Verify flow exists immediately (eager creation)
# @name verifyEagerFlowExists
GET {{base}}/api/v1/flows/{{eagerFlowId}}
X-API-Key: {{apiKey}}
###
# Verify:
# - Flow exists immediately
# - aliases contains "@hero-flow"
###############################################################################
# TEST 5: List All Flows
###############################################################################
### Step 5.1: List flows
# @name listFlows
GET {{base}}/api/v1/flows
X-API-Key: {{apiKey}}
###
# Verify:
# - Returns array of flows
# - Contains our lazyFlowId and eagerFlowId
###############################################################################
# TEST 6: Get Flow with Computed Counts
###############################################################################
### Step 6.1: Get flow details
# @name getFlowDetails
GET {{base}}/api/v1/flows/{{lazyFlowId}}
X-API-Key: {{apiKey}}
###
# Verify:
# - generationCount is number (should be 2)
# - imageCount is number (should be 2)
# - aliases object present
###############################################################################
# TEST 7: List Flow Generations
###############################################################################
### Step 7.1: Get flow's generations
# @name getFlowGenerations
GET {{base}}/api/v1/flows/{{lazyFlowId}}/generations
X-API-Key: {{apiKey}}
###
# Verify:
# - Returns array of generations
# - Contains 2 generations from lazy flow tests
###############################################################################
# TEST 8: List Flow Images
###############################################################################
### Step 8.1: Get flow's images
# @name getFlowImages
GET {{base}}/api/v1/flows/{{lazyFlowId}}/images
X-API-Key: {{apiKey}}
###
# Verify:
# - Returns array of images
# - Contains output images from generations
###############################################################################
# TEST 9: Update Flow Aliases
###############################################################################
### Step 9.1: Update flow aliases
# @name updateFlowAliases
PUT {{base}}/api/v1/flows/{{lazyFlowId}}/aliases
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"aliases": {
"@latest": "{{checkLazyGen2.response.body.$.data.outputImageId}}",
"@best": "{{checkLazyGen2.response.body.$.data.outputImageId}}"
}
}
###
# Verify:
# - Returns updated flow with new aliases
# - aliases contains @latest and @best
### Step 9.2: Verify aliases set
# @name verifyAliasesSet
GET {{base}}/api/v1/flows/{{lazyFlowId}}
X-API-Key: {{apiKey}}
###
###############################################################################
# TEST 10: Remove Specific Flow Alias
###############################################################################
### Step 10.1: Delete @best alias
# @name deleteFlowAlias
DELETE {{base}}/api/v1/flows/{{lazyFlowId}}/aliases/@best
X-API-Key: {{apiKey}}
###
### Step 10.2: Verify alias removed
# @name verifyAliasRemoved
GET {{base}}/api/v1/flows/{{lazyFlowId}}
X-API-Key: {{apiKey}}
###
# Verify:
# - @best not in aliases
# - @latest still in aliases
###############################################################################
# TEST 11: Regenerate Flow
# Regenerates the most recent generation in a flow
###############################################################################
### Step 11.1: Trigger regeneration
# @name regenerateFlow
POST {{base}}/api/v1/flows/{{lazyFlowId}}/regenerate
Content-Type: application/json
X-API-Key: {{apiKey}}
{}
###
# Verify:
# - Returns new generation object
# - New generation is in the same flow
###############################################################################
# NOTES
###############################################################################
#
# Lazy Flow Pattern (Section 4.1):
# 1. First request without flowId -> return generated flowId, but DO NOT create in DB
# 2. Any request with valid flowId -> create flow in DB if doesn't exist
# 3. If flowAlias specified -> create flow immediately (eager creation)
#
# Flow Aliases:
# - Stored in flow.aliases JSONB field
# - Map alias names to image IDs
# - Can be updated via PUT /flows/:id/aliases
# - Individual aliases deleted via DELETE /flows/:id/aliases/:alias
#

590
tests/api/04-aliases.rest Normal file
View File

@ -0,0 +1,590 @@
@base = http://localhost:3000
@apiKey = bnt_727d2f4f72bd03ed96da5278bb971a00cb0a2454d4d70f9748b5c39f3f69d88d
###############################################################################
# ALIAS RESOLUTION TESTS
# Tests: 3-Tier Alias Resolution (Technical -> Flow -> Project)
#
# Test Coverage:
# 1. Technical alias @last
# 2. Technical alias @first
# 3. Technical alias @upload
# 4. Technical alias requires flowId
# 5. Flow-scoped alias resolution
# 6. Project-scoped alias resolution
# 7. Alias precedence (flow > project)
# 8. Reserved aliases cannot be assigned
# 9. Alias reassignment removes old
# 10. Same alias in different flows
# 11. Technical alias in generation prompt
# 12. Upload with both project and flow alias
###############################################################################
###############################################################################
# SETUP: Create Test Flow
###############################################################################
### Setup: Create flow for alias tests
# @name setupGen
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Setup image for alias tests",
"aspectRatio": "1:1",
"flowAlias": "@alias-test-flow"
}
###
@aliasFlowId = {{setupGen.response.body.$.data.flowId}}
@setupGenId = {{setupGen.response.body.$.data.id}}
### Poll setup generation
# @name checkSetupGen
GET {{base}}/api/v1/generations/{{setupGenId}}
X-API-Key: {{apiKey}}
###
@setupImageId = {{checkSetupGen.response.body.$.data.outputImageId}}
###############################################################################
# TEST 1: Technical Alias @last
# Resolves to last generated image in flow
###############################################################################
### Step 1.1: Resolve @last (requires flowId)
# @name resolveLast
GET {{base}}/api/v1/images/@last?flowId={{aliasFlowId}}
X-API-Key: {{apiKey}}
###
# Verify:
# - Returns image (status 200)
# - Returns the most recently generated image in the flow
###############################################################################
# TEST 2: Technical Alias @first
# Resolves to first generated image in flow
###############################################################################
### Step 2.1: Resolve @first (requires flowId)
# @name resolveFirst
GET {{base}}/api/v1/images/@first?flowId={{aliasFlowId}}
X-API-Key: {{apiKey}}
###
# Verify:
# - Returns image (status 200)
# - Returns the first generated image in the flow
###############################################################################
# TEST 3: Technical Alias @upload
# Resolves to last uploaded image in flow
###############################################################################
### Step 3.1: Upload image to flow
# @name uploadForTest
POST {{base}}/api/v1/images/upload
X-API-Key: {{apiKey}}
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="test-image.png"
Content-Type: image/png
< ./fixture/test-image.png
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="flowId"
{{aliasFlowId}}
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="description"
Uploaded for @upload test
------WebKitFormBoundary7MA4YWxkTrZu0gW--
###
@uploadedImageId = {{uploadForTest.response.body.$.data.id}}
### Step 3.2: Resolve @upload (requires flowId)
# @name resolveUpload
GET {{base}}/api/v1/images/@upload?flowId={{aliasFlowId}}
X-API-Key: {{apiKey}}
###
# Verify:
# - Returns image (status 200)
# - Returns uploaded image (source = "uploaded")
###############################################################################
# TEST 4: Technical Alias Requires Flow Context
# @last, @first, @upload require flowId parameter
###############################################################################
### Step 4.1: Try @last without flowId (should fail)
# @name resolveLastNoFlow
GET {{base}}/api/v1/images/@last
X-API-Key: {{apiKey}}
###
# Expected: 404 with error "Technical aliases require flowId"
###############################################################################
# TEST 5: Flow-Scoped Alias Resolution
###############################################################################
### Step 5.1: Create generation with flow alias
# @name flowAliasGen
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Image for flow alias test",
"aspectRatio": "1:1",
"flowId": "{{aliasFlowId}}",
"flowAlias": "@flow-hero"
}
###
@flowAliasGenId = {{flowAliasGen.response.body.$.data.id}}
### Step 5.2: Poll generation
# @name checkFlowAliasGen
GET {{base}}/api/v1/generations/{{flowAliasGenId}}
X-API-Key: {{apiKey}}
###
@flowHeroImageId = {{checkFlowAliasGen.response.body.$.data.outputImageId}}
### Step 5.3: Resolve flow alias
# @name resolveFlowAlias
GET {{base}}/api/v1/images/@flow-hero?flowId={{aliasFlowId}}
X-API-Key: {{apiKey}}
###
# Verify:
# - Returns the image from step 5.1
# - Only works with flowId parameter
###############################################################################
# TEST 6: Project-Scoped Alias Resolution
###############################################################################
### Step 6.1: Create generation with project alias
# @name projectAliasGen
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Image for project alias test",
"aspectRatio": "1:1",
"alias": "@project-logo",
"flowId": null
}
###
@projectAliasGenId = {{projectAliasGen.response.body.$.data.id}}
### Step 6.2: Poll generation
# @name checkProjectAliasGen
GET {{base}}/api/v1/generations/{{projectAliasGenId}}
X-API-Key: {{apiKey}}
###
@projectLogoImageId = {{checkProjectAliasGen.response.body.$.data.outputImageId}}
### Step 6.3: Resolve project alias (no flowId needed)
# @name resolveProjectAlias
GET {{base}}/api/v1/images/@project-logo
X-API-Key: {{apiKey}}
###
# Verify:
# - Returns the image from step 6.1
# - Works without flowId parameter
###############################################################################
# TEST 7: Alias Precedence (Flow > Project)
###############################################################################
### Step 7.1: Create project-scoped alias @priority-test
# @name priorityProject
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Project scoped image for priority test",
"aspectRatio": "1:1",
"alias": "@priority-test",
"flowId": null
}
###
@priorityProjectGenId = {{priorityProject.response.body.$.data.id}}
### Step 7.2: Poll generation
# @name checkPriorityProject
GET {{base}}/api/v1/generations/{{priorityProjectGenId}}
X-API-Key: {{apiKey}}
###
@priorityProjectImageId = {{checkPriorityProject.response.body.$.data.outputImageId}}
### Step 7.3: Create flow-scoped alias @priority-test (same name)
# @name priorityFlow
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Flow scoped image for priority test",
"aspectRatio": "1:1",
"flowId": "{{aliasFlowId}}",
"flowAlias": "@priority-test"
}
###
@priorityFlowGenId = {{priorityFlow.response.body.$.data.id}}
### Step 7.4: Poll generation
# @name checkPriorityFlow
GET {{base}}/api/v1/generations/{{priorityFlowGenId}}
X-API-Key: {{apiKey}}
###
@priorityFlowImageId = {{checkPriorityFlow.response.body.$.data.outputImageId}}
### Step 7.5: Resolve WITHOUT flowId (should get project)
# @name resolvePriorityNoFlow
GET {{base}}/api/v1/images/@priority-test
X-API-Key: {{apiKey}}
###
# Verify: Returns project image ({{priorityProjectImageId}})
### Step 7.6: Resolve WITH flowId (should get flow)
# @name resolvePriorityWithFlow
GET {{base}}/api/v1/images/@priority-test?flowId={{aliasFlowId}}
X-API-Key: {{apiKey}}
###
# Verify: Returns flow image ({{priorityFlowImageId}})
###############################################################################
# TEST 8: Reserved Aliases Cannot Be Assigned
###############################################################################
### Step 8.1: Try to use @last as alias (should fail or warn)
# @name reservedLast
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Test reserved alias",
"aspectRatio": "1:1",
"alias": "@last"
}
###
# Expected: 400 validation error OR generation succeeds but @last not assigned
### Step 8.2: Try to use @first as alias
# @name reservedFirst
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Test reserved alias",
"aspectRatio": "1:1",
"alias": "@first"
}
###
### Step 8.3: Try to use @upload as alias
# @name reservedUpload
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Test reserved alias",
"aspectRatio": "1:1",
"alias": "@upload"
}
###
###############################################################################
# TEST 9: Alias Reassignment (Override Behavior)
###############################################################################
### Step 9.1: Create first image with alias
# @name reassign1
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "First image for reassign test",
"aspectRatio": "1:1",
"alias": "@reassign-test",
"flowId": null
}
###
@reassign1GenId = {{reassign1.response.body.$.data.id}}
### Step 9.2: Poll first generation
# @name checkReassign1
GET {{base}}/api/v1/generations/{{reassign1GenId}}
X-API-Key: {{apiKey}}
###
@reassign1ImageId = {{checkReassign1.response.body.$.data.outputImageId}}
### Step 9.3: Create second image with SAME alias
# @name reassign2
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Second image for reassign test",
"aspectRatio": "1:1",
"alias": "@reassign-test",
"flowId": null
}
###
@reassign2GenId = {{reassign2.response.body.$.data.id}}
### Step 9.4: Poll second generation
# @name checkReassign2
GET {{base}}/api/v1/generations/{{reassign2GenId}}
X-API-Key: {{apiKey}}
###
@reassign2ImageId = {{checkReassign2.response.body.$.data.outputImageId}}
### Step 9.5: Resolve alias (should be second image)
# @name resolveReassign
GET {{base}}/api/v1/images/@reassign-test
X-API-Key: {{apiKey}}
###
# Verify: Returns second image ({{reassign2ImageId}})
### Step 9.6: Check first image lost alias
# @name checkFirstLostAlias
GET {{base}}/api/v1/images/{{reassign1ImageId}}
X-API-Key: {{apiKey}}
###
# Verify: alias = null
###############################################################################
# TEST 10: Same Alias in Different Flows
###############################################################################
### Step 10.1: Create flow 1 with @shared-name alias
# @name sharedFlow1
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Flow 1 image with shared name",
"aspectRatio": "1:1",
"flowAlias": "@shared-name"
}
###
@sharedFlow1Id = {{sharedFlow1.response.body.$.data.flowId}}
@sharedGen1Id = {{sharedFlow1.response.body.$.data.id}}
### Step 10.2: Poll generation 1
# @name checkSharedGen1
GET {{base}}/api/v1/generations/{{sharedGen1Id}}
X-API-Key: {{apiKey}}
###
@sharedImage1Id = {{checkSharedGen1.response.body.$.data.outputImageId}}
### Step 10.3: Create flow 2 with SAME @shared-name alias
# @name sharedFlow2
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Flow 2 image with shared name",
"aspectRatio": "1:1",
"flowAlias": "@shared-name"
}
###
@sharedFlow2Id = {{sharedFlow2.response.body.$.data.flowId}}
@sharedGen2Id = {{sharedFlow2.response.body.$.data.id}}
### Step 10.4: Poll generation 2
# @name checkSharedGen2
GET {{base}}/api/v1/generations/{{sharedGen2Id}}
X-API-Key: {{apiKey}}
###
@sharedImage2Id = {{checkSharedGen2.response.body.$.data.outputImageId}}
### Step 10.5: Resolve @shared-name in flow 1
# @name resolveSharedFlow1
GET {{base}}/api/v1/images/@shared-name?flowId={{sharedFlow1Id}}
X-API-Key: {{apiKey}}
###
# Verify: Returns {{sharedImage1Id}}
### Step 10.6: Resolve @shared-name in flow 2
# @name resolveSharedFlow2
GET {{base}}/api/v1/images/@shared-name?flowId={{sharedFlow2Id}}
X-API-Key: {{apiKey}}
###
# Verify: Returns {{sharedImage2Id}} (different from flow 1)
###############################################################################
# TEST 11: Technical Alias in Generation Prompt
###############################################################################
### Step 11.1: Generate using @last in prompt
# @name techAliasPrompt
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "New variation based on @last",
"aspectRatio": "1:1",
"flowId": "{{aliasFlowId}}"
}
###
@techAliasGenId = {{techAliasPrompt.response.body.$.data.id}}
### Step 11.2: Poll generation
# @name checkTechAliasGen
GET {{base}}/api/v1/generations/{{techAliasGenId}}
X-API-Key: {{apiKey}}
###
# Verify:
# - status = "success"
# - referencedImages contains @last alias
###############################################################################
# TEST 12: Upload with Both Project and Flow Alias
###############################################################################
### Step 12.1: Upload with both aliases
# @name dualAliasUpload
POST {{base}}/api/v1/images/upload
X-API-Key: {{apiKey}}
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="test-image.png"
Content-Type: image/png
< ./fixture/test-image.png
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="alias"
@dual-project
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="flowId"
{{aliasFlowId}}
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="flowAlias"
@dual-flow
------WebKitFormBoundary7MA4YWxkTrZu0gW--
###
@dualAliasImageId = {{dualAliasUpload.response.body.$.data.id}}
### Step 12.2: Resolve project alias
# @name resolveDualProject
GET {{base}}/api/v1/images/@dual-project
X-API-Key: {{apiKey}}
###
# Verify: Returns {{dualAliasImageId}}
### Step 12.3: Resolve flow alias
# @name resolveDualFlow
GET {{base}}/api/v1/images/@dual-flow?flowId={{aliasFlowId}}
X-API-Key: {{apiKey}}
###
# Verify: Returns {{dualAliasImageId}} (same image)
###############################################################################
# NOTES
###############################################################################
#
# 3-Tier Alias Resolution Order:
# 1. Technical (@last, @first, @upload) - require flowId
# 2. Flow-scoped (stored in flow.aliases) - require flowId
# 3. Project-scoped (stored in images.alias) - no flowId needed
#
# Alias Format:
# - Must start with @
# - Alphanumeric + hyphens only
# - Reserved: @last, @first, @upload
#
# Override Behavior (Section 5.2):
# - New alias assignment takes priority
# - Previous image loses its alias
# - Previous image is NOT deleted
#

217
tests/api/05-live.rest Normal file
View File

@ -0,0 +1,217 @@
@base = http://localhost:3000
@apiKey = bnt_727d2f4f72bd03ed96da5278bb971a00cb0a2454d4d70f9748b5c39f3f69d88d
###############################################################################
# LIVE URL & SCOPE MANAGEMENT TESTS
# Tests: Live generation with caching, Scope management
#
# Test Coverage:
# 1. Create live scope
# 2. List all scopes
# 3. Get scope details
# 4. Update scope settings
# 5. Live URL - basic generation
# 6. Regenerate scope images
# 7. Delete scope
###############################################################################
###############################################################################
# TEST 1: Create Live Scope
###############################################################################
### Step 1.1: Create scope
# @name createScope
POST {{base}}/api/v1/live/scopes
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"slug": "test-scope",
"allowNewGenerations": true,
"newGenerationsLimit": 50
}
###
# Verify:
# - Returns scope object
# - slug = "test-scope"
# - allowNewGenerations = true
# - newGenerationsLimit = 50
###############################################################################
# TEST 2: List All Scopes
###############################################################################
### Step 2.1: List scopes
# @name listScopes
GET {{base}}/api/v1/live/scopes
X-API-Key: {{apiKey}}
###
# Verify:
# - Returns array of scopes
# - Contains "test-scope"
###############################################################################
# TEST 3: Get Scope Details
###############################################################################
### Step 3.1: Get scope by slug
# @name getScope
GET {{base}}/api/v1/live/scopes/test-scope
X-API-Key: {{apiKey}}
###
# Verify:
# - Returns scope object
# - slug = "test-scope"
# - currentGenerations is number
###############################################################################
# TEST 4: Update Scope Settings
###############################################################################
### Step 4.1: Disable new generations
# @name updateScopeDisable
PUT {{base}}/api/v1/live/scopes/test-scope
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"allowNewGenerations": false,
"newGenerationsLimit": 100
}
###
# Verify:
# - allowNewGenerations = false
# - newGenerationsLimit = 100
### Step 4.2: Re-enable for testing
# @name updateScopeEnable
PUT {{base}}/api/v1/live/scopes/test-scope
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"allowNewGenerations": true
}
###
###############################################################################
# TEST 5: Live URL - Basic Generation
# GET /api/v1/live?prompt=...
# Returns image bytes directly with cache headers
###############################################################################
### Step 5.1: Generate via live URL
# @name liveGenerate
GET {{base}}/api/v1/live?prompt=A%20simple%20blue%20square%20on%20white%20background
X-API-Key: {{apiKey}}
###
# Verify:
# - Returns 200
# - Response is image bytes (Content-Type: image/*)
# - X-Cache-Status header (HIT or MISS)
### Step 5.2: Same prompt again (should be cached)
# @name liveGenerateCached
GET {{base}}/api/v1/live?prompt=A%20simple%20blue%20square%20on%20white%20background
X-API-Key: {{apiKey}}
###
# Verify:
# - X-Cache-Status: HIT
# - Faster response time
### Step 5.3: Different prompt
# @name liveGenerateNew
GET {{base}}/api/v1/live?prompt=A%20red%20circle%20on%20black%20background
X-API-Key: {{apiKey}}
###
# Verify:
# - X-Cache-Status: MISS (new prompt)
### Step 5.4: With aspect ratio
# @name liveGenerateWithAspect
GET {{base}}/api/v1/live?prompt=A%20landscape%20scene&aspectRatio=16:9
X-API-Key: {{apiKey}}
###
###############################################################################
# TEST 6: Regenerate Scope Images
###############################################################################
### Step 6.1: Trigger regeneration
# @name regenerateScope
POST {{base}}/api/v1/live/scopes/test-scope/regenerate
Content-Type: application/json
X-API-Key: {{apiKey}}
{}
###
# Verify:
# - Returns 200
# - Regeneration triggered
###############################################################################
# TEST 7: Delete Scope
###############################################################################
### Step 7.1: Delete scope
# @name deleteScope
DELETE {{base}}/api/v1/live/scopes/test-scope
X-API-Key: {{apiKey}}
###
# Verify:
# - Returns 200
### Step 7.2: Verify deleted (should 404)
# @name verifyScopeDeleted
GET {{base}}/api/v1/live/scopes/test-scope
X-API-Key: {{apiKey}}
###
# Expected: 404 Not Found
###############################################################################
# NOTES
###############################################################################
#
# Live URL Endpoint:
# - GET /api/v1/live?prompt=...
# - Returns image bytes directly (not JSON)
# - Supports prompt caching via SHA-256 hash
#
# Response Headers:
# - Content-Type: image/jpeg (or image/png, etc.)
# - X-Cache-Status: HIT | MISS
# - X-Cache-Hit-Count: number (on HIT)
# - X-Generation-Id: UUID (on MISS)
# - X-Image-Id: UUID
# - Cache-Control: public, max-age=31536000
#
# Scope Management:
# - Scopes group generations for management
# - allowNewGenerations controls if new prompts generate
# - newGenerationsLimit caps generations per scope
#

View File

@ -0,0 +1,315 @@
@base = http://localhost:3000
@apiKey = bnt_727d2f4f72bd03ed96da5278bb971a00cb0a2454d4d70f9748b5c39f3f69d88d
###############################################################################
# EDGE CASES & VALIDATION TESTS
# Tests: Input validation, Error handling, Edge cases
#
# Test Coverage:
# 1. Invalid alias format
# 2. Invalid aspect ratio
# 3. Missing required fields
# 4. 404 for non-existent resources
# 5. Regenerate generation
# 6. CDN endpoints
###############################################################################
###############################################################################
# TEST 1: Invalid Alias Format
# Aliases must start with @ and contain only alphanumeric + hyphens
###############################################################################
### Step 1.1: Alias without @ symbol (should fail)
# @name invalidNoAt
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Test invalid alias",
"aspectRatio": "1:1",
"alias": "no-at-symbol"
}
###
# Expected: 400 validation error OR 500 with alias error
### Step 1.2: Alias with spaces (should fail)
# @name invalidWithSpaces
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Test invalid alias",
"aspectRatio": "1:1",
"alias": "@has spaces"
}
###
# Expected: 400 validation error
### Step 1.3: Alias with special characters (should fail)
# @name invalidSpecialChars
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Test invalid alias",
"aspectRatio": "1:1",
"alias": "@special!chars"
}
###
# Expected: 400 validation error
### Step 1.4: Empty alias (should fail or be ignored)
# @name invalidEmpty
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Test invalid alias",
"aspectRatio": "1:1",
"alias": ""
}
###
###############################################################################
# TEST 2: Invalid Aspect Ratio
###############################################################################
### Step 2.1: Invalid aspect ratio string
# @name invalidAspectRatio
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Test invalid aspect ratio",
"aspectRatio": "invalid"
}
###
# Expected: 400 validation error
### Step 2.2: Unsupported aspect ratio
# @name unsupportedAspectRatio
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Test unsupported aspect ratio",
"aspectRatio": "5:7"
}
###
# Expected: 400 validation error (only 1:1, 16:9, 9:16, 4:3, 3:4, 21:9 supported)
###############################################################################
# TEST 3: Missing Required Fields
###############################################################################
### Step 3.1: Missing prompt
# @name missingPrompt
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"aspectRatio": "1:1"
}
###
# Expected: 400 - "Prompt is required"
### Step 3.2: Empty body
# @name emptyBody
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{}
###
# Expected: 400 - "Prompt is required"
###############################################################################
# TEST 4: 404 for Non-Existent Resources
###############################################################################
### Step 4.1: Non-existent image
# @name notFoundImage
GET {{base}}/api/v1/images/00000000-0000-0000-0000-000000000000
X-API-Key: {{apiKey}}
###
# Expected: 404 Not Found
### Step 4.2: Non-existent generation
# @name notFoundGeneration
GET {{base}}/api/v1/generations/00000000-0000-0000-0000-000000000000
X-API-Key: {{apiKey}}
###
# Expected: 404 Not Found
### Step 4.3: Non-existent flow
# @name notFoundFlow
GET {{base}}/api/v1/flows/00000000-0000-0000-0000-000000000000
X-API-Key: {{apiKey}}
###
# Expected: 404 Not Found
### Step 4.4: Non-existent alias
# @name notFoundAlias
GET {{base}}/api/v1/images/@non-existent-alias
X-API-Key: {{apiKey}}
###
# Expected: 404 - "Alias '@non-existent-alias' not found"
###############################################################################
# TEST 5: Regenerate Generation
###############################################################################
### Step 5.1: Create generation for regenerate test
# @name createForRegen
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Test image for regenerate",
"aspectRatio": "1:1"
}
###
@regenSourceId = {{createForRegen.response.body.$.data.id}}
### Step 5.2: Poll until success
# @name checkForRegen
GET {{base}}/api/v1/generations/{{regenSourceId}}
X-API-Key: {{apiKey}}
###
### Step 5.3: Regenerate
# @name regenerateGen
POST {{base}}/api/v1/generations/{{regenSourceId}}/regenerate
Content-Type: application/json
X-API-Key: {{apiKey}}
{}
###
# Verify:
# - Returns new generation
# - New generation has same prompt
### Step 5.4: Regenerate non-existent generation (should 404)
# @name regenerateNotFound
POST {{base}}/api/v1/generations/00000000-0000-0000-0000-000000000000/regenerate
Content-Type: application/json
X-API-Key: {{apiKey}}
{}
###
# Expected: 404 Not Found
###############################################################################
# TEST 6: CDN Endpoints
###############################################################################
### Step 6.1: CDN image by path (if implemented)
# @name cdnImage
GET {{base}}/api/v1/cdn/default/test-project/generated/2024-01/test.jpg
X-API-Key: {{apiKey}}
###
# Note: Endpoint structure check only - actual paths depend on storage
### Step 6.2: Health check
# @name healthCheck
GET {{base}}/health
###
# Expected: 200 with status info
###############################################################################
# TEST 7: Authentication Errors
###############################################################################
### Step 7.1: Missing API key
# @name noApiKey
GET {{base}}/api/v1/generations
###
# Expected: 401 Unauthorized
### Step 7.2: Invalid API key
# @name invalidApiKey
GET {{base}}/api/v1/generations
X-API-Key: bnt_invalid_key_12345
###
# Expected: 401 Unauthorized
###############################################################################
# TEST 8: Malformed Requests
###############################################################################
### Step 8.1: Invalid JSON
# @name invalidJson
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{invalid json}
###
# Expected: 400 Bad Request
### Step 8.2: Wrong content type
# @name wrongContentType
POST {{base}}/api/v1/generations
Content-Type: text/plain
X-API-Key: {{apiKey}}
prompt=test&aspectRatio=1:1
###
###############################################################################
# NOTES
###############################################################################
#
# Validation Rules:
# - Prompt: required, non-empty string
# - Aspect ratio: must be supported (1:1, 16:9, 9:16, 4:3, 3:4, 21:9)
# - Alias: must start with @, alphanumeric + hyphens only
# - UUID: must be valid UUID format
#
# Error Responses:
# - 400: Validation error (missing/invalid fields)
# - 401: Authentication error (missing/invalid API key)
# - 404: Resource not found
# - 429: Rate limit exceeded
# - 500: Internal server error
#

View File

@ -0,0 +1,259 @@
@base = http://localhost:3000
@apiKey = bnt_727d2f4f72bd03ed96da5278bb971a00cb0a2454d4d70f9748b5c39f3f69d88d
###############################################################################
# KNOWN ISSUES TESTS
# These tests document known bugs and implementation gaps
#
# ⚠️ EXPECTED TO FAIL until issues are fixed
#
# Test Coverage:
# 1. Project alias on flow image
# 2. Flow delete cascades non-aliased images
# 3. Flow delete preserves aliased images
# 4. Flow delete cascades generations
###############################################################################
###############################################################################
# ISSUE 1: Project Alias on Flow Image
# An image in a flow should be able to have a project-scoped alias
###############################################################################
### Step 1.1: Create image with both flow and project alias
# @name issue1Gen
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Image in flow with project alias",
"aspectRatio": "1:1",
"flowAlias": "@flow-test",
"alias": "@project-test"
}
###
@issue1FlowId = {{issue1Gen.response.body.$.data.flowId}}
@issue1GenId = {{issue1Gen.response.body.$.data.id}}
### Step 1.2: Poll generation
# @name checkIssue1Gen
GET {{base}}/api/v1/generations/{{issue1GenId}}
X-API-Key: {{apiKey}}
###
@issue1ImageId = {{checkIssue1Gen.response.body.$.data.outputImageId}}
### Step 1.3: Resolve project alias (via deprecated /resolve endpoint)
# @name resolveProjectOnFlow
GET {{base}}/api/v1/images/resolve/@project-test
X-API-Key: {{apiKey}}
###
# BUG: Project alias on flow image should be resolvable
# Expected: Returns image with id = {{issue1ImageId}}
### Step 1.4: Resolve project alias (via direct path - Section 6.2)
# @name resolveProjectOnFlowDirect
GET {{base}}/api/v1/images/@project-test
X-API-Key: {{apiKey}}
###
# This should work after Section 6.2 implementation
###############################################################################
# ISSUE 2: Flow Delete Cascades Non-Aliased Images
# When deleting a flow, images without project alias should be deleted
###############################################################################
### Step 2.1: Create flow with non-aliased image
# @name issue2Gen1
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "No alias image",
"aspectRatio": "1:1",
"flowAlias": "@issue-flow"
}
###
@issue2FlowId = {{issue2Gen1.response.body.$.data.flowId}}
@issue2Gen1Id = {{issue2Gen1.response.body.$.data.id}}
### Step 2.2: Poll generation
# @name checkIssue2Gen1
GET {{base}}/api/v1/generations/{{issue2Gen1Id}}
X-API-Key: {{apiKey}}
###
@issue2Image1Id = {{checkIssue2Gen1.response.body.$.data.outputImageId}}
### Step 2.3: Add aliased image to same flow
# @name issue2Gen2
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "With alias image",
"aspectRatio": "1:1",
"flowId": "{{issue2FlowId}}",
"alias": "@protected-image"
}
###
@issue2Gen2Id = {{issue2Gen2.response.body.$.data.id}}
### Step 2.4: Poll generation
# @name checkIssue2Gen2
GET {{base}}/api/v1/generations/{{issue2Gen2Id}}
X-API-Key: {{apiKey}}
###
@issue2Image2Id = {{checkIssue2Gen2.response.body.$.data.outputImageId}}
### Step 2.5: Delete flow
# @name deleteIssue2Flow
DELETE {{base}}/api/v1/flows/{{issue2FlowId}}
X-API-Key: {{apiKey}}
###
### Step 2.6: Check non-aliased image (should be 404)
# @name checkIssue2Image1Deleted
GET {{base}}/api/v1/images/{{issue2Image1Id}}
X-API-Key: {{apiKey}}
###
# Expected: 404 - Non-aliased image should be deleted with flow
### Step 2.7: Check aliased image (should still exist)
# @name checkIssue2Image2Exists
GET {{base}}/api/v1/images/{{issue2Image2Id}}
X-API-Key: {{apiKey}}
###
# Expected: 200 - Aliased image should be preserved
###############################################################################
# ISSUE 3: Flow Delete Preserves Aliased Images
# Aliased images should have flowId set to null after flow deletion
###############################################################################
### Step 3.1: Create flow with aliased image
# @name issue3Gen
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Protected image",
"aspectRatio": "1:1",
"flowAlias": "@test-flow-2",
"alias": "@keep-this"
}
###
@issue3FlowId = {{issue3Gen.response.body.$.data.flowId}}
@issue3GenId = {{issue3Gen.response.body.$.data.id}}
### Step 3.2: Poll generation
# @name checkIssue3Gen
GET {{base}}/api/v1/generations/{{issue3GenId}}
X-API-Key: {{apiKey}}
###
@issue3ImageId = {{checkIssue3Gen.response.body.$.data.outputImageId}}
### Step 3.3: Delete flow
# @name deleteIssue3Flow
DELETE {{base}}/api/v1/flows/{{issue3FlowId}}
X-API-Key: {{apiKey}}
###
### Step 3.4: Check aliased image (should exist with flowId=null)
# @name checkIssue3ImagePreserved
GET {{base}}/api/v1/images/{{issue3ImageId}}
X-API-Key: {{apiKey}}
###
# Expected: 200 with flowId = null
# BUG: flowId might not be set to null
###############################################################################
# ISSUE 4: Flow Delete Cascades Generations
# Generations should be deleted when flow is deleted
###############################################################################
### Step 4.1: Create flow with generation
# @name issue4Gen
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "Test generation",
"aspectRatio": "1:1",
"flowAlias": "@gen-flow"
}
###
@issue4FlowId = {{issue4Gen.response.body.$.data.flowId}}
@issue4GenId = {{issue4Gen.response.body.$.data.id}}
### Step 4.2: Poll generation
# @name checkIssue4Gen
GET {{base}}/api/v1/generations/{{issue4GenId}}
X-API-Key: {{apiKey}}
###
### Step 4.3: Delete flow
# @name deleteIssue4Flow
DELETE {{base}}/api/v1/flows/{{issue4FlowId}}
X-API-Key: {{apiKey}}
###
### Step 4.4: Check generation (should be 404)
# @name checkIssue4GenDeleted
GET {{base}}/api/v1/generations/{{issue4GenId}}
X-API-Key: {{apiKey}}
###
# Expected: 404 - Generation should be deleted with flow
###############################################################################
# NOTES
###############################################################################
#
# Flow Deletion Cascade (per api-refactoring-final.md):
# - Flow record → DELETE
# - All generations → DELETE
# - Images without alias → DELETE (with MinIO cleanup)
# - Images with project alias → KEEP (unlink: flowId = NULL)
#
# Known Issues:
# 1. Project alias on flow images may not resolve properly
# 2. Flow deletion may not properly cascade deletions
# 3. Aliased images may not have flowId set to null
#
# These tests document expected behavior that may not be implemented yet.
#

View File

@ -0,0 +1,248 @@
@base = http://localhost:3000
@apiKey = bnt_727d2f4f72bd03ed96da5278bb971a00cb0a2454d4d70f9748b5c39f3f69d88d
###############################################################################
# AUTO-ENHANCE TESTS
# Tests: Prompt auto-enhancement feature
#
# Test Coverage:
# 1. Generate without autoEnhance param (defaults to true)
# 2. Generate with autoEnhance: false
# 3. Generate with autoEnhance: true
# 4. Verify enhancement quality
# 5. List generations with autoEnhance field
# 6. Verify response structure
###############################################################################
###############################################################################
# TEST 1: Generate Without autoEnhance Parameter
# Should default to true (enhancement enabled)
###############################################################################
### Step 1.1: Create generation without autoEnhance param
# @name genDefaultEnhance
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "a simple test image",
"aspectRatio": "1:1"
}
###
@genDefaultId = {{genDefaultEnhance.response.body.$.data.id}}
### Step 1.2: Poll generation
# @name checkGenDefault
GET {{base}}/api/v1/generations/{{genDefaultId}}
X-API-Key: {{apiKey}}
###
# Verify:
# - autoEnhance = true
# - originalPrompt = "a simple test image"
# - prompt != originalPrompt (was enhanced)
###############################################################################
# TEST 2: Generate with autoEnhance: false
# Should NOT enhance the prompt
###############################################################################
### Step 2.1: Create generation with autoEnhance: false
# @name genNoEnhance
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "another test image",
"aspectRatio": "1:1",
"autoEnhance": false
}
###
@genNoEnhanceId = {{genNoEnhance.response.body.$.data.id}}
### Step 2.2: Poll generation
# @name checkGenNoEnhance
GET {{base}}/api/v1/generations/{{genNoEnhanceId}}
X-API-Key: {{apiKey}}
###
# Verify:
# - autoEnhance = false
# - originalPrompt = "another test image"
# - prompt = "another test image" (same, NOT enhanced)
###############################################################################
# TEST 3: Generate with autoEnhance: true
# Should enhance the prompt
###############################################################################
### Step 3.1: Create generation with explicit autoEnhance: true
# @name genExplicitEnhance
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "third test image",
"aspectRatio": "1:1",
"autoEnhance": true
}
###
@genExplicitId = {{genExplicitEnhance.response.body.$.data.id}}
### Step 3.2: Poll generation
# @name checkGenExplicit
GET {{base}}/api/v1/generations/{{genExplicitId}}
X-API-Key: {{apiKey}}
###
# Verify:
# - autoEnhance = true
# - originalPrompt = "third test image"
# - prompt != originalPrompt (was enhanced)
# - prompt is longer and more descriptive
###############################################################################
# TEST 4: Verify Enhancement Quality
# Enhanced prompt should be longer and more descriptive
###############################################################################
### Step 4.1: Get enhanced generation
# @name getEnhancedGen
GET {{base}}/api/v1/generations/{{genDefaultId}}
X-API-Key: {{apiKey}}
###
# Verify:
# - Enhanced prompt is longer than original
# - Enhanced prompt may contain: "photorealistic", "detailed", "scene", etc.
# - Compare: prompt.length > originalPrompt.length
###############################################################################
# TEST 5: List Generations with autoEnhance Field
###############################################################################
### Step 5.1: List all generations
# @name listGens
GET {{base}}/api/v1/generations
X-API-Key: {{apiKey}}
###
# Verify:
# - Each generation has autoEnhance field (boolean)
# - Some generations have autoEnhance = true
# - Some generations have autoEnhance = false
### Step 5.2: Filter by status to see recent ones
# @name listSuccessGens
GET {{base}}/api/v1/generations?status=success&limit=10
X-API-Key: {{apiKey}}
###
###############################################################################
# TEST 6: Verify Response Structure
###############################################################################
### Step 6.1: Get generation and check fields
# @name verifyStructure
GET {{base}}/api/v1/generations/{{genDefaultId}}
X-API-Key: {{apiKey}}
###
# Expected fields:
# - prompt: string (final prompt, possibly enhanced)
# - originalPrompt: string (original input prompt)
# - autoEnhance: boolean (whether enhancement was applied)
# - status: string
# - outputImageId: string (on success)
# - processingTimeMs: number (on completion)
###############################################################################
# ADDITIONAL TEST CASES
###############################################################################
### Complex prompt that might be enhanced differently
# @name complexPrompt
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "a cat sitting on a windowsill",
"aspectRatio": "16:9"
}
###
@complexId = {{complexPrompt.response.body.$.data.id}}
### Check complex prompt enhancement
# @name checkComplexPrompt
GET {{base}}/api/v1/generations/{{complexId}}
X-API-Key: {{apiKey}}
###
# Verify: Enhanced prompt should add details like lighting, perspective, style, etc.
### Short prompt enhancement
# @name shortPrompt
POST {{base}}/api/v1/generations
Content-Type: application/json
X-API-Key: {{apiKey}}
{
"prompt": "sunset",
"aspectRatio": "21:9"
}
###
@shortId = {{shortPrompt.response.body.$.data.id}}
### Check short prompt enhancement
# @name checkShortPrompt
GET {{base}}/api/v1/generations/{{shortId}}
X-API-Key: {{apiKey}}
###
# Verify: Very short prompts should be significantly enhanced
###############################################################################
# NOTES
###############################################################################
#
# Auto-Enhance Feature:
# - Default: autoEnhance = true (prompts are enhanced by AI)
# - Set autoEnhance: false to disable enhancement
# - Enhanced prompts are more detailed and descriptive
#
# Response Fields:
# - prompt: The final prompt (enhanced if autoEnhance was true)
# - originalPrompt: The user's original input
# - autoEnhance: Boolean flag indicating if enhancement was applied
#
# Enhancement adds:
# - Descriptive adjectives
# - Lighting and atmosphere details
# - Perspective and composition hints
# - Style and rendering suggestions
#