fix bruno collection
This commit is contained in:
@@ -0,0 +1,606 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
title: market-screener.bruno
|
||||
version: 1.0.0
|
||||
paths:
|
||||
/api/analyze:
|
||||
post:
|
||||
summary: 'Analyze — Validation: empty tickers (expect 400)'
|
||||
operationId: >-
|
||||
users_kanna_documents_bruno_market_screener_api_-_1_llm_analysis_analyze_validation-_empty_tickers_expect_400_bru
|
||||
description: 'Schema validation: minItems: 1. Expect 400.'
|
||||
tags:
|
||||
- LLM Analysis
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
parameters:
|
||||
- name: Content-Type
|
||||
in: header
|
||||
description: ''
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: application/json
|
||||
requestBody:
|
||||
$ref: '#/components/requestBodies/analyze_validation_empty_tickers_expect_400'
|
||||
/health:
|
||||
get:
|
||||
summary: Health Check
|
||||
operationId: >-
|
||||
users_kanna_documents_bruno_market_screener_api_-_1_health_health_check_bru
|
||||
description: 'Confirms the server is running. Expects { status: ''ok'' }.'
|
||||
tags:
|
||||
- Health
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
/api/finance/market-context:
|
||||
get:
|
||||
summary: Get Market Context
|
||||
operationId: >-
|
||||
users_kanna_documents_bruno_market_screener_api_-_1_market_context_get_market_context_bru
|
||||
description: >-
|
||||
Returns live benchmark data: S&P500 price, 10Y rate, VIX, SPY P/E, XLK
|
||||
P/E, XLRE yield, LQD spread. Served from 1-hour in-memory cache.
|
||||
tags:
|
||||
- Market Context
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
/api/calls:
|
||||
post:
|
||||
summary: Create Market Call
|
||||
operationId: >-
|
||||
users_kanna_documents_bruno_market_screener_api_-_1_market_calls_create_market_call_bru
|
||||
description: >-
|
||||
Creates a market thesis call. Snapshots current prices + screener
|
||||
signals at creation time for future comparison.
|
||||
|
||||
|
||||
The test script saves the returned ID to the {{callId}} collection
|
||||
variable for use in subsequent requests.
|
||||
tags:
|
||||
- Market Calls
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
parameters:
|
||||
- name: Content-Type
|
||||
in: header
|
||||
description: ''
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: application/json
|
||||
requestBody:
|
||||
$ref: '#/components/requestBodies/create_market_call'
|
||||
get:
|
||||
summary: List Calls (empty or existing)
|
||||
operationId: >-
|
||||
users_kanna_documents_bruno_market_screener_api_-_1_market_calls_list_calls_empty_or_existing_bru
|
||||
description: >-
|
||||
Returns all market calls sorted newest first. Returns { calls: [] } if
|
||||
none exist yet.
|
||||
tags:
|
||||
- Market Calls
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
/api/calls/{{callId}}:
|
||||
delete:
|
||||
summary: Delete Call
|
||||
operationId: >-
|
||||
users_kanna_documents_bruno_market_screener_api_-_1_market_calls_delete_call_bru
|
||||
description: >-
|
||||
Deletes the call created earlier. Returns { ok: true }. Requires
|
||||
{{callId}} to be set.
|
||||
tags:
|
||||
- Market Calls
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
get:
|
||||
summary: Get Call by ID (with current re-screen)
|
||||
operationId: >-
|
||||
users_kanna_documents_bruno_market_screener_api_-_1_market_calls_get_call_by_id_with_current_re-screen_bru
|
||||
description: >-
|
||||
Fetches the call and re-screens all tickers to show how signal/price has
|
||||
changed since creation.
|
||||
|
||||
|
||||
Returns: original call fields + `current` map of ticker → { price,
|
||||
signal, inflatedVerdict, fundamentalVerdict, pe, roe, fcf }.
|
||||
|
||||
|
||||
Depends on {{callId}} being set by the Create Market Call request.
|
||||
tags:
|
||||
- Market Calls
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
/api/calls/00000000-0000-0000-0000-000000000000:
|
||||
get:
|
||||
summary: Get Call — Non-existent ID (expect 404)
|
||||
operationId: >-
|
||||
users_kanna_documents_bruno_market_screener_api_-_1_market_calls_get_call_non-existent_id_expect_404_bru
|
||||
description: A UUID that doesn't exist. Expect 404.
|
||||
tags:
|
||||
- Market Calls
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
/api/calls/calendar:
|
||||
get:
|
||||
summary: Get Earnings Calendar (call tickers)
|
||||
operationId: >-
|
||||
users_kanna_documents_bruno_market_screener_api_-_1_market_calls_get_earnings_calendar_call_tickers_bru
|
||||
description: >-
|
||||
Returns upcoming earnings dates and dividend events for all tickers
|
||||
across all saved calls.
|
||||
|
||||
|
||||
Optional query param ?tickers=AAPL,MSFT to restrict to specific tickers.
|
||||
tags:
|
||||
- Market Calls
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
/api/calls/calendar?tickers=AAPL,MSFT:
|
||||
get:
|
||||
summary: Get Earnings Calendar — Specific Tickers
|
||||
operationId: >-
|
||||
users_kanna_documents_bruno_market_screener_api_-_1_market_calls_get_earnings_calendar_specific_tickers_bru
|
||||
description: Calendar for specific tickers regardless of saved calls.
|
||||
tags:
|
||||
- Market Calls
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
parameters:
|
||||
- name: tickers
|
||||
in: query
|
||||
description: ''
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: AAPL,MSFT
|
||||
/api/finance/holdings:
|
||||
post:
|
||||
summary: 'Add Holding — Validation: missing shares (expect 400)'
|
||||
operationId: >-
|
||||
users_kanna_documents_bruno_market_screener_api_-_1_portfolio_add_holding_validation-_missing_shares_expect_400_bru
|
||||
description: 'Schema validation: shares is required. Expect 400.'
|
||||
tags:
|
||||
- Portfolio
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
parameters:
|
||||
- name: Content-Type
|
||||
in: header
|
||||
description: ''
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: application/json
|
||||
requestBody:
|
||||
$ref: >-
|
||||
#/components/requestBodies/add_holding_validation_missing_shares_expect_400
|
||||
/api/finance/portfolio:
|
||||
get:
|
||||
summary: Get Portfolio
|
||||
operationId: >-
|
||||
users_kanna_documents_bruno_market_screener_api_-_1_portfolio_get_portfolio_bru
|
||||
description: >-
|
||||
Screens all non-crypto holdings via Yahoo Finance, then cross-references
|
||||
with signals to produce buy/hold/sell advice.
|
||||
|
||||
|
||||
Each row has: ticker, signal, advice, reason, currentPrice, marketValue,
|
||||
gainLossPct.
|
||||
|
||||
Also returns marketContext.
|
||||
|
||||
|
||||
Note: first call after server start may be slow (benchmark cache cold).
|
||||
tags:
|
||||
- Portfolio
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
/api/finance/holdings/AAPL:
|
||||
delete:
|
||||
summary: Remove Holding — AAPL
|
||||
operationId: >-
|
||||
users_kanna_documents_bruno_market_screener_api_-_1_portfolio_remove_holding_aapl_bru
|
||||
description: 'Removes the AAPL holding from portfolio.json. Expect { ok: true }.'
|
||||
tags:
|
||||
- Portfolio
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
/api/finance/holdings/ZZZZZZ:
|
||||
delete:
|
||||
summary: Remove Holding — Non-existent (expect 404)
|
||||
operationId: >-
|
||||
users_kanna_documents_bruno_market_screener_api_-_1_portfolio_remove_holding_non-existent_expect_404_bru
|
||||
description: Ticker does not exist in portfolio. Expect 404.
|
||||
tags:
|
||||
- Portfolio
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
/api/screen/catalysts:
|
||||
get:
|
||||
summary: Get Catalysts
|
||||
operationId: >-
|
||||
users_kanna_documents_bruno_market_screener_api_-_1_screener_get_catalysts_bru
|
||||
description: >-
|
||||
Fetches today's Yahoo Finance news, extracts ticker symbols mentioned,
|
||||
and returns { tickers, stories }. May take 3-5s as it queries multiple
|
||||
news endpoints.
|
||||
tags:
|
||||
- Screener
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
/api/screen:
|
||||
post:
|
||||
summary: 'Screen — Validation: over 50 tickers (expect 400)'
|
||||
operationId: >-
|
||||
users_kanna_documents_bruno_market_screener_api_-_1_screener_screen_validation-_over_50_tickers_expect_400_bru
|
||||
description: 'Schema validation: maxItems: 50. 51 tickers should return 400.'
|
||||
tags:
|
||||
- Screener
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
parameters:
|
||||
- name: Content-Type
|
||||
in: header
|
||||
description: ''
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: application/json
|
||||
requestBody:
|
||||
$ref: >-
|
||||
#/components/requestBodies/screen_validation_over_50_tickers_expect_400
|
||||
servers:
|
||||
- url: http://localhost:3000
|
||||
description: Base Server
|
||||
components:
|
||||
schemas:
|
||||
analyze_tickers:
|
||||
type: object
|
||||
properties:
|
||||
tickers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
tickers:
|
||||
- NVDA
|
||||
- AMD
|
||||
- INTC
|
||||
analyze_validation_empty_tickers_expect_400:
|
||||
type: object
|
||||
properties:
|
||||
tickers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
tickers: []
|
||||
create_call_validation_short_thesis_expect_400:
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
quarter:
|
||||
type: string
|
||||
thesis:
|
||||
type: string
|
||||
tickers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
title: Test
|
||||
quarter: Q1
|
||||
thesis: short
|
||||
tickers:
|
||||
- AAPL
|
||||
create_market_call:
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
quarter:
|
||||
type: string
|
||||
thesis:
|
||||
type: string
|
||||
tickers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
title: AI Infrastructure Supercycle
|
||||
quarter: Q3 2025
|
||||
thesis: >-
|
||||
Hyperscaler capex remains elevated through 2026 driven by LLM training
|
||||
demand. NVDA, MSFT and AMD are the primary beneficiaries. Entry here
|
||||
as NVDA pulled back 15% from high.
|
||||
tickers:
|
||||
- NVDA
|
||||
- MSFT
|
||||
- AMD
|
||||
add_holding_aapl:
|
||||
type: object
|
||||
properties:
|
||||
ticker:
|
||||
type: string
|
||||
shares:
|
||||
type: integer
|
||||
costBasis:
|
||||
type: integer
|
||||
type:
|
||||
type: string
|
||||
source:
|
||||
type: string
|
||||
example:
|
||||
ticker: AAPL
|
||||
shares: 10
|
||||
costBasis: 150
|
||||
type: stock
|
||||
source: Robinhood
|
||||
add_holding_btc-usd_crypto_no_scoring:
|
||||
type: object
|
||||
properties:
|
||||
ticker:
|
||||
type: string
|
||||
shares:
|
||||
type: number
|
||||
costBasis:
|
||||
type: integer
|
||||
type:
|
||||
type: string
|
||||
source:
|
||||
type: string
|
||||
example:
|
||||
ticker: BTC-USD
|
||||
shares: 0.1
|
||||
costBasis: 50000
|
||||
type: crypto
|
||||
source: Coinbase
|
||||
add_holding_voo_etf:
|
||||
type: object
|
||||
properties:
|
||||
ticker:
|
||||
type: string
|
||||
shares:
|
||||
type: integer
|
||||
costBasis:
|
||||
type: integer
|
||||
type:
|
||||
type: string
|
||||
source:
|
||||
type: string
|
||||
example:
|
||||
ticker: VOO
|
||||
shares: 5
|
||||
costBasis: 420
|
||||
type: etf
|
||||
source: Vanguard
|
||||
add_holding_validation_missing_shares_expect_400:
|
||||
type: object
|
||||
properties:
|
||||
ticker:
|
||||
type: string
|
||||
example:
|
||||
ticker: MSFT
|
||||
screen_mixed_stock_etf_bond:
|
||||
type: object
|
||||
properties:
|
||||
tickers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
tickers:
|
||||
- AAPL
|
||||
- MSFT
|
||||
- GOOGL
|
||||
- VOO
|
||||
- AGG
|
||||
screen_reit_tests_p_ffo_scoring_path:
|
||||
type: object
|
||||
properties:
|
||||
tickers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
tickers:
|
||||
- O
|
||||
- VICI
|
||||
- PLD
|
||||
screen_tech_stocks_tests_technology_sector_override:
|
||||
type: object
|
||||
properties:
|
||||
tickers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
tickers:
|
||||
- NVDA
|
||||
- META
|
||||
- AMZN
|
||||
- TSLA
|
||||
screen_validation_empty_tickers_expect_400:
|
||||
type: object
|
||||
properties:
|
||||
tickers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
tickers: []
|
||||
screen_validation_over_50_tickers_expect_400:
|
||||
type: object
|
||||
properties:
|
||||
tickers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
tickers:
|
||||
- A
|
||||
- B
|
||||
- C
|
||||
- D
|
||||
- E
|
||||
- F
|
||||
- G
|
||||
- H
|
||||
- I
|
||||
- J
|
||||
- K
|
||||
- L
|
||||
- M
|
||||
- 'N'
|
||||
- O
|
||||
- P
|
||||
- Q
|
||||
- R
|
||||
- S
|
||||
- T
|
||||
- U
|
||||
- V
|
||||
- W
|
||||
- X
|
||||
- 'Y'
|
||||
- Z
|
||||
- AA
|
||||
- BB
|
||||
- CC
|
||||
- DD
|
||||
- EE
|
||||
- FF
|
||||
- GG
|
||||
- HH
|
||||
- II
|
||||
- JJ
|
||||
- KK
|
||||
- LL
|
||||
- MM
|
||||
- NN
|
||||
- OO
|
||||
- PP
|
||||
- QQ
|
||||
- RR
|
||||
- SS
|
||||
- TT
|
||||
- UU
|
||||
- VV
|
||||
- WW
|
||||
- XX
|
||||
- YY
|
||||
requestBodies:
|
||||
analyze_tickers:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/analyze_tickers'
|
||||
description: ''
|
||||
required: true
|
||||
analyze_validation_empty_tickers_expect_400:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/analyze_validation_empty_tickers_expect_400'
|
||||
description: ''
|
||||
required: true
|
||||
create_call_validation_short_thesis_expect_400:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: >-
|
||||
#/components/schemas/create_call_validation_short_thesis_expect_400
|
||||
description: ''
|
||||
required: true
|
||||
create_market_call:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/create_market_call'
|
||||
description: ''
|
||||
required: true
|
||||
add_holding_aapl:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/add_holding_aapl'
|
||||
description: ''
|
||||
required: true
|
||||
add_holding_btc-usd_crypto_no_scoring:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/add_holding_btc-usd_crypto_no_scoring'
|
||||
description: ''
|
||||
required: true
|
||||
add_holding_voo_etf:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/add_holding_voo_etf'
|
||||
description: ''
|
||||
required: true
|
||||
add_holding_validation_missing_shares_expect_400:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: >-
|
||||
#/components/schemas/add_holding_validation_missing_shares_expect_400
|
||||
description: ''
|
||||
required: true
|
||||
screen_mixed_stock_etf_bond:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/screen_mixed_stock_etf_bond'
|
||||
description: ''
|
||||
required: true
|
||||
screen_reit_tests_p_ffo_scoring_path:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/screen_reit_tests_p_ffo_scoring_path'
|
||||
description: ''
|
||||
required: true
|
||||
screen_tech_stocks_tests_technology_sector_override:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: >-
|
||||
#/components/schemas/screen_tech_stocks_tests_technology_sector_override
|
||||
description: ''
|
||||
required: true
|
||||
screen_validation_empty_tickers_expect_400:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/screen_validation_empty_tickers_expect_400'
|
||||
description: ''
|
||||
required: true
|
||||
screen_validation_over_50_tickers_expect_400:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/screen_validation_over_50_tickers_expect_400'
|
||||
description: ''
|
||||
required: true
|
||||
securitySchemes: {}
|
||||
@@ -0,0 +1,737 @@
|
||||
{
|
||||
"info": {
|
||||
"name": "Market Screener API",
|
||||
"description": "Full test suite for the market-screener Fastify server.\n\nBase URL is stored in the `baseUrl` collection variable (default: http://localhost:3000).\n\nWorkflow order for a clean session:\n1. Health Check\n2. Screen Tickers (creates results to inspect)\n3. Get Market Context\n4. Get Catalysts\n5. Add Holdings → Get Portfolio\n6. Create Market Call → Get Call → Calendar\n7. Analyze\n8. Cleanup (delete holding, delete call)",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"variable": [
|
||||
{
|
||||
"key": "baseUrl",
|
||||
"value": "http://localhost:3000",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "callId",
|
||||
"value": "",
|
||||
"type": "string",
|
||||
"description": "Set automatically by the Create Market Call test script"
|
||||
}
|
||||
],
|
||||
"item": [
|
||||
{
|
||||
"name": "Health",
|
||||
"item": [
|
||||
{
|
||||
"name": "Health Check",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "{{baseUrl}}/health",
|
||||
"description": "Confirms the server is running. Expects { status: 'ok' }."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 200', () => pm.response.to.have.status(200));",
|
||||
"pm.test('Body has status ok', () => {",
|
||||
" const json = pm.response.json();",
|
||||
" pm.expect(json.status).to.eql('ok');",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Screener",
|
||||
"item": [
|
||||
{
|
||||
"name": "Screen — Mixed (STOCK + ETF + BOND)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "{{baseUrl}}/api/screen",
|
||||
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"tickers\": [\"AAPL\", \"MSFT\", \"GOOGL\", \"VOO\", \"AGG\"]\n}"
|
||||
},
|
||||
"description": "Screens a mixed set of stocks, an ETF (VOO), and a bond ETF (AGG).\n\nExpect each result to have:\n- asset.ticker, asset.type, asset.currentPrice\n- asset.displayMetrics (Cap Tier, Style, Analyst, DCF Safety, 52W fields)\n- fundamental + inflated score labels\n- signal (Strong Buy / Momentum / Speculation / Neutral / Avoid)\n- marketContext (riskFreeRate, rateRegime, benchmarks)"
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 200', () => pm.response.to.have.status(200));",
|
||||
"",
|
||||
"pm.test('Response shape is valid', () => {",
|
||||
" const json = pm.response.json();",
|
||||
" pm.expect(json).to.have.all.keys('STOCK', 'ETF', 'BOND', 'ERROR', 'marketContext');",
|
||||
"});",
|
||||
"",
|
||||
"pm.test('marketContext has required fields', () => {",
|
||||
" const ctx = pm.response.json().marketContext;",
|
||||
" pm.expect(ctx).to.have.property('riskFreeRate');",
|
||||
" pm.expect(ctx).to.have.property('rateRegime');",
|
||||
" pm.expect(ctx.benchmarks).to.have.all.keys('marketPE', 'techPE', 'reitYield', 'igSpread');",
|
||||
"});",
|
||||
"",
|
||||
"pm.test('Each stock has expert fields in displayMetrics', () => {",
|
||||
" const stocks = pm.response.json().STOCK;",
|
||||
" if (stocks.length === 0) return;",
|
||||
" const dm = stocks[0].asset.displayMetrics;",
|
||||
" pm.expect(dm).to.have.property('Cap Tier');",
|
||||
" pm.expect(dm).to.have.property('Style');",
|
||||
"});",
|
||||
"",
|
||||
"pm.test('Each stock has a signal', () => {",
|
||||
" pm.response.json().STOCK.forEach(r => {",
|
||||
" pm.expect(r.signal).to.be.oneOf([",
|
||||
" '✅ Strong Buy', '⚡ Momentum', '⚠️ Speculation', '🔄 Neutral', '❌ Avoid'",
|
||||
" ]);",
|
||||
" });",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Screen — Tech Stocks (tests TECHNOLOGY sector override)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "{{baseUrl}}/api/screen",
|
||||
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"tickers\": [\"NVDA\", \"META\", \"AMZN\", \"TSLA\"]\n}"
|
||||
},
|
||||
"description": "High-growth tech tickers to validate DCF margin-of-safety scoring, analyst consensus, and 52W movement fields. Expect 'High Growth' in Style field for most."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 200', () => pm.response.to.have.status(200));",
|
||||
"",
|
||||
"pm.test('52W fields present when data available', () => {",
|
||||
" pm.response.json().STOCK.forEach(r => {",
|
||||
" const dm = r.asset.displayMetrics;",
|
||||
" // at least one 52W field should be populated",
|
||||
" const has52W = dm['52W Chg'] || dm['From High'] || dm['52W Pos'];",
|
||||
" pm.expect(has52W).to.be.ok;",
|
||||
" });",
|
||||
"});",
|
||||
"",
|
||||
"pm.test('DCF or analyst field present for stocks with FCF', () => {",
|
||||
" // Not all stocks will have positive FCF, but at least one should have DCF",
|
||||
" const stocks = pm.response.json().STOCK;",
|
||||
" const hasDcf = stocks.some(r => r.asset.displayMetrics['DCF Safety'] != null);",
|
||||
" const hasAnalyst = stocks.some(r => r.asset.displayMetrics['Analyst'] != null);",
|
||||
" pm.expect(hasDcf || hasAnalyst).to.be.true;",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Screen — REIT (tests P/FFO scoring path)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "{{baseUrl}}/api/screen",
|
||||
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"tickers\": [\"O\", \"VICI\", \"PLD\"]\n}"
|
||||
},
|
||||
"description": "REITs should show sector REIT, P/FFO in displayMetrics (not P/E), and be scored on yield rather than the standard Graham gates."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 200', () => pm.response.to.have.status(200));",
|
||||
"",
|
||||
"pm.test('REITs land in STOCK bucket with REIT sector', () => {",
|
||||
" pm.response.json().STOCK.forEach(r => {",
|
||||
" pm.expect(r.asset.displayMetrics['Sector']).to.eql('REIT');",
|
||||
" });",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Screen — Validation: empty tickers (expect 400)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "{{baseUrl}}/api/screen",
|
||||
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"tickers\": []\n}"
|
||||
},
|
||||
"description": "Schema validation: minItems: 1. Expect 400 Bad Request."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 400 for empty array', () => pm.response.to.have.status(400));"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Screen — Validation: over 50 tickers (expect 400)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "{{baseUrl}}/api/screen",
|
||||
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"tickers\": [\"A\",\"B\",\"C\",\"D\",\"E\",\"F\",\"G\",\"H\",\"I\",\"J\",\"K\",\"L\",\"M\",\"N\",\"O\",\"P\",\"Q\",\"R\",\"S\",\"T\",\"U\",\"V\",\"W\",\"X\",\"Y\",\"Z\",\"AA\",\"BB\",\"CC\",\"DD\",\"EE\",\"FF\",\"GG\",\"HH\",\"II\",\"JJ\",\"KK\",\"LL\",\"MM\",\"NN\",\"OO\",\"PP\",\"QQ\",\"RR\",\"SS\",\"TT\",\"UU\",\"VV\",\"WW\",\"XX\",\"YY\"]\n}"
|
||||
},
|
||||
"description": "Schema validation: maxItems: 50. 51 tickers should return 400."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 400 for 51 tickers', () => pm.response.to.have.status(400));"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Get Catalysts",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "{{baseUrl}}/api/screen/catalysts",
|
||||
"description": "Fetches today's Yahoo Finance news, extracts ticker symbols mentioned, and returns { tickers, stories }. May take 3-5s as it queries multiple news endpoints."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 200', () => pm.response.to.have.status(200));",
|
||||
"",
|
||||
"pm.test('Response has tickers and stories arrays', () => {",
|
||||
" const json = pm.response.json();",
|
||||
" pm.expect(json.tickers).to.be.an('array');",
|
||||
" pm.expect(json.stories).to.be.an('array');",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Market Context",
|
||||
"item": [
|
||||
{
|
||||
"name": "Get Market Context",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "{{baseUrl}}/api/finance/market-context",
|
||||
"description": "Returns live benchmark data: S&P500 price, 10Y rate, VIX, SPY P/E, XLK P/E, XLRE yield, LQD spread. Served from 1-hour in-memory cache."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 200', () => pm.response.to.have.status(200));",
|
||||
"",
|
||||
"pm.test('All benchmark fields present', () => {",
|
||||
" const json = pm.response.json();",
|
||||
" pm.expect(json).to.have.property('sp500Price');",
|
||||
" pm.expect(json).to.have.property('riskFreeRate');",
|
||||
" pm.expect(json).to.have.property('vixLevel');",
|
||||
" pm.expect(json).to.have.property('rateRegime');",
|
||||
" pm.expect(json.rateRegime).to.be.oneOf(['LOW', 'NORMAL', 'HIGH']);",
|
||||
" pm.expect(json.benchmarks).to.have.all.keys('marketPE', 'techPE', 'reitYield', 'igSpread');",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Portfolio",
|
||||
"item": [
|
||||
{
|
||||
"name": "Add Holding — AAPL",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "{{baseUrl}}/api/finance/holdings",
|
||||
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"ticker\": \"AAPL\",\n \"shares\": 10,\n \"costBasis\": 150.00,\n \"type\": \"stock\",\n \"source\": \"Robinhood\"\n}"
|
||||
},
|
||||
"description": "Adds or updates an AAPL holding in portfolio.json. Returns the saved holding with status 201."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 201', () => pm.response.to.have.status(201));",
|
||||
"",
|
||||
"pm.test('Saved holding matches input', () => {",
|
||||
" const json = pm.response.json();",
|
||||
" pm.expect(json.ticker).to.eql('AAPL');",
|
||||
" pm.expect(json.shares).to.eql(10);",
|
||||
" pm.expect(json.costBasis).to.eql(150);",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Add Holding — VOO (ETF)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "{{baseUrl}}/api/finance/holdings",
|
||||
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"ticker\": \"VOO\",\n \"shares\": 5,\n \"costBasis\": 420.00,\n \"type\": \"etf\",\n \"source\": \"Vanguard\"\n}"
|
||||
}
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 201', () => pm.response.to.have.status(201));"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Add Holding — BTC-USD (Crypto, no scoring)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "{{baseUrl}}/api/finance/holdings",
|
||||
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"ticker\": \"BTC-USD\",\n \"shares\": 0.1,\n \"costBasis\": 50000,\n \"type\": \"crypto\",\n \"source\": \"Coinbase\"\n}"
|
||||
},
|
||||
"description": "Crypto is priced via Yahoo but not fundamentally scored. Advice column shows '—' for signal."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 201', () => pm.response.to.have.status(201));"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Add Holding — Validation: missing shares (expect 400)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "{{baseUrl}}/api/finance/holdings",
|
||||
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"ticker\": \"MSFT\"\n}"
|
||||
},
|
||||
"description": "Schema validation: shares is required. Expect 400."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 400 when shares missing', () => pm.response.to.have.status(400));"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Get Portfolio",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "{{baseUrl}}/api/finance/portfolio",
|
||||
"description": "Screens all non-crypto holdings via Yahoo Finance, then cross-references with signals to produce buy/hold/sell advice.\n\nEach row has: ticker, signal, advice, reason, currentPrice, marketValue, gainLossPct.\nAlso returns marketContext.\n\nNote: first call after server start may be slow (benchmark cache cold)."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 200', () => pm.response.to.have.status(200));",
|
||||
"",
|
||||
"pm.test('Advice array present', () => {",
|
||||
" const json = pm.response.json();",
|
||||
" pm.expect(json.advice).to.be.an('array');",
|
||||
" pm.expect(json.advice.length).to.be.greaterThan(0);",
|
||||
"});",
|
||||
"",
|
||||
"pm.test('Each advice row has required fields', () => {",
|
||||
" pm.response.json().advice.forEach(row => {",
|
||||
" pm.expect(row).to.have.property('ticker');",
|
||||
" pm.expect(row).to.have.property('advice');",
|
||||
" pm.expect(row).to.have.property('reason');",
|
||||
" });",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Remove Holding — AAPL",
|
||||
"request": {
|
||||
"method": "DELETE",
|
||||
"url": "{{baseUrl}}/api/finance/holdings/AAPL",
|
||||
"description": "Removes the AAPL holding from portfolio.json. Expect { ok: true }."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 200', () => pm.response.to.have.status(200));",
|
||||
"pm.test('ok true', () => pm.expect(pm.response.json().ok).to.be.true);"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Remove Holding — Non-existent (expect 404)",
|
||||
"request": {
|
||||
"method": "DELETE",
|
||||
"url": "{{baseUrl}}/api/finance/holdings/ZZZZZZ",
|
||||
"description": "Ticker does not exist in portfolio. Expect 404."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 404', () => pm.response.to.have.status(404));"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Market Calls",
|
||||
"item": [
|
||||
{
|
||||
"name": "List Calls (empty or existing)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "{{baseUrl}}/api/calls",
|
||||
"description": "Returns all market calls sorted newest first. Returns { calls: [] } if none exist yet."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 200', () => pm.response.to.have.status(200));",
|
||||
"pm.test('calls is array', () => pm.expect(pm.response.json().calls).to.be.an('array'));"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Create Market Call",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "{{baseUrl}}/api/calls",
|
||||
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"title\": \"AI Infrastructure Supercycle\",\n \"quarter\": \"Q3 2025\",\n \"thesis\": \"Hyperscaler capex remains elevated through 2026 driven by LLM training demand. NVDA, MSFT and AMD are the primary beneficiaries. Entry here as NVDA pulled back 15% from high.\",\n \"tickers\": [\"NVDA\", \"MSFT\", \"AMD\"]\n}"
|
||||
},
|
||||
"description": "Creates a market thesis call. Snapshots current prices + screener signals at creation time for future comparison.\n\nThe test script saves the returned ID to the {{callId}} collection variable for use in subsequent requests."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 201', () => pm.response.to.have.status(201));",
|
||||
"",
|
||||
"pm.test('Call has id, snapshot, and createdAt', () => {",
|
||||
" const json = pm.response.json();",
|
||||
" pm.expect(json).to.have.property('id');",
|
||||
" pm.expect(json).to.have.property('snapshot');",
|
||||
" pm.expect(json).to.have.property('createdAt');",
|
||||
" // Save for downstream tests",
|
||||
" pm.collectionVariables.set('callId', json.id);",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Get Call by ID (with current re-screen)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "{{baseUrl}}/api/calls/{{callId}}",
|
||||
"description": "Fetches the call and re-screens all tickers to show how signal/price has changed since creation.\n\nReturns: original call fields + `current` map of ticker → { price, signal, inflatedVerdict, fundamentalVerdict, pe, roe, fcf }.\n\nDepends on {{callId}} being set by the Create Market Call request."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 200', () => pm.response.to.have.status(200));",
|
||||
"",
|
||||
"pm.test('Response has snapshot and current', () => {",
|
||||
" const json = pm.response.json();",
|
||||
" pm.expect(json).to.have.property('snapshot');",
|
||||
" pm.expect(json).to.have.property('current');",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Get Call — Non-existent ID (expect 404)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "{{baseUrl}}/api/calls/00000000-0000-0000-0000-000000000000",
|
||||
"description": "A UUID that doesn't exist. Expect 404."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 404', () => pm.response.to.have.status(404));"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Get Earnings Calendar (call tickers)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "{{baseUrl}}/api/calls/calendar",
|
||||
"description": "Returns upcoming earnings dates and dividend events for all tickers across all saved calls.\n\nOptional query param ?tickers=AAPL,MSFT to restrict to specific tickers."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 200', () => pm.response.to.have.status(200));",
|
||||
"",
|
||||
"pm.test('events is array', () => {",
|
||||
" pm.expect(pm.response.json().events).to.be.an('array');",
|
||||
"});",
|
||||
"",
|
||||
"pm.test('Events have expected shape', () => {",
|
||||
" pm.response.json().events.forEach(e => {",
|
||||
" pm.expect(e).to.have.property('ticker');",
|
||||
" pm.expect(e).to.have.property('type');",
|
||||
" pm.expect(e.type).to.be.oneOf(['earnings', 'dividend', 'exdividend']);",
|
||||
" pm.expect(e).to.have.property('date');",
|
||||
" pm.expect(e).to.have.property('isPast');",
|
||||
" });",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Get Earnings Calendar — Specific Tickers",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/calls/calendar?tickers=AAPL,MSFT",
|
||||
"query": [
|
||||
{ "key": "tickers", "value": "AAPL,MSFT" }
|
||||
]
|
||||
},
|
||||
"description": "Calendar for specific tickers regardless of saved calls."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 200', () => pm.response.to.have.status(200));",
|
||||
"pm.test('Only AAPL and MSFT events', () => {",
|
||||
" pm.response.json().events.forEach(e => {",
|
||||
" pm.expect(e.ticker).to.be.oneOf(['AAPL', 'MSFT']);",
|
||||
" });",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Create Call — Validation: short thesis (expect 400)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "{{baseUrl}}/api/calls",
|
||||
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"title\": \"Test\",\n \"quarter\": \"Q1\",\n \"thesis\": \"short\",\n \"tickers\": [\"AAPL\"]\n}"
|
||||
},
|
||||
"description": "Schema: thesis minLength: 10. Expect 400."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 400 for short thesis', () => pm.response.to.have.status(400));"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Delete Call",
|
||||
"request": {
|
||||
"method": "DELETE",
|
||||
"url": "{{baseUrl}}/api/calls/{{callId}}",
|
||||
"description": "Deletes the call created earlier. Returns { ok: true }. Requires {{callId}} to be set."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 200', () => pm.response.to.have.status(200));",
|
||||
"pm.test('ok true', () => pm.expect(pm.response.json().ok).to.be.true);"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Delete Call — Already Deleted (expect 404)",
|
||||
"request": {
|
||||
"method": "DELETE",
|
||||
"url": "{{baseUrl}}/api/calls/{{callId}}",
|
||||
"description": "Second delete of the same ID. Expect 404."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 404 on double delete', () => pm.response.to.have.status(404));"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "LLM Analysis",
|
||||
"item": [
|
||||
{
|
||||
"name": "Analyze Tickers",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "{{baseUrl}}/api/analyze",
|
||||
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"tickers\": [\"NVDA\", \"AMD\", \"INTC\"]\n}"
|
||||
},
|
||||
"description": "Fetches Yahoo Finance news for the given tickers, then sends headlines to Claude (Haiku) for analysis.\n\nReturns: { analysis: { summary, sentiment, affectedIndustries, relatedTickers } }\n\nReturns 400 if ANTHROPIC_API_KEY is not set in .env.\nReturns { analysis: null, reason: 'no_stories' } if no news found."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 200 or 400', () => {",
|
||||
" pm.expect(pm.response.code).to.be.oneOf([200, 400]);",
|
||||
"});",
|
||||
"",
|
||||
"if (pm.response.code === 200) {",
|
||||
" pm.test('Analysis shape valid (if present)', () => {",
|
||||
" const json = pm.response.json();",
|
||||
" if (json.analysis) {",
|
||||
" pm.expect(json.analysis).to.have.property('summary');",
|
||||
" pm.expect(json.analysis).to.have.property('sentiment');",
|
||||
" pm.expect(json.analysis.sentiment).to.be.oneOf(['BULLISH', 'NEUTRAL', 'BEARISH']);",
|
||||
" pm.expect(json.analysis.affectedIndustries).to.be.an('array');",
|
||||
" pm.expect(json.analysis.relatedTickers).to.be.an('array');",
|
||||
" }",
|
||||
" });",
|
||||
"} else {",
|
||||
" pm.test('400 means API key not set', () => {",
|
||||
" pm.expect(pm.response.json().error).to.include('ANTHROPIC_API_KEY');",
|
||||
" });",
|
||||
"}"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Analyze — Validation: empty tickers (expect 400)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "{{baseUrl}}/api/analyze",
|
||||
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"tickers\": []\n}"
|
||||
},
|
||||
"description": "Schema validation: minItems: 1. Expect 400."
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status 400', () => pm.response.to.have.status(400));"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user