2026-09-09 20:24:47 +08:00
import copy
import json
from contextlib import asynccontextmanager
import httpx
import pytest
2026-09-10 16:07:18 +08:00
from pydantic_ai.messages import ModelResponse , TextPart , UserPromptPart
2026-09-09 20:24:47 +08:00
from pydantic_ai.models.function import FunctionModel
from app.alphas import upsert_alpha
from app.models import Account , AISettings , Alpha , Job , Research , SelfCorrelation
from app.security import cipher
2026-09-10 16:36:35 +08:00
from app.submission import HEADINGS , Description , comparable_description , fingerprint , source
2026-09-09 20:24:47 +08:00
from app.worldquant import WqClient
from tests.conftest import alpha
FIELDS = {
"idea" : "Short term price reversal is a hypothesis for this signal." ,
"data_rationale" : "Close prices represent the observed price history of each instrument." ,
"operator_rationale" : "The delta measures five day change, rank compares stocks and negation reverses the ordering." ,
}
2026-09-10 16:07:18 +08:00
# Captured from the failing gateway response: 649 characters including template labels.
ACCRUAL_FIELDS = {
"idea" : "Combines ranked accrual-related signals and subtracts a ranked score signal, hypothesizing that higher accrual measures and lower fscore values identify relatively attractive stocks." ,
"data_rationale" : "The selected fields are named cashflow accruals, WC accruals, and fscore; their exact definitions are not provided, so interpretation is qualified. A 120-period backfill supplies missing observations." ,
"operator_rationale" : "ts_backfill extends the latest available value, rank makes fields cross-sectionally comparable, addition combines the accrual signals, and subtraction gives the fscore component a negative contribution." ,
}
2026-09-09 20:24:47 +08:00
CHECKS = [{ "name" : "PROD_CORRELATION" , "result" : "FAIL" , "value" : 0.8 , "limit" : 0.7 }]
2026-09-10 16:36:35 +08:00
@pytest.mark.parametrize (
"text" ,
[
"plain text" ,
"Idea: actual idea \n Rationale for data used: \n Rationale for operators used:" ,
"Idea: \n Rationale for data used: actual data \n Rationale for operators used:" ,
"Idea: \n Rationale for data used: \n Rationale for operators used: actual operator" ,
"Idea: \n Rationale for data used:" ,
"Rationale for data used: \n Idea: \n Rationale for operators used:" ,
],
)
def test_substantive_or_unknown_descriptions_remain_exact ( text ):
assert comparable_description ( text ) == text
original = source ( alpha ( regular = { "code" : "rank(close)" , "description" : text }))
changed = copy . deepcopy ( original )
changed [ "sections" ][ "regular" ][ "description" ] = text + " "
assert fingerprint ( original ) != fingerprint ( changed )
assert original [ "sections" ][ "regular" ][ "description" ] == text
2026-09-09 20:24:47 +08:00
class Platform :
def __init__ ( self , raw ):
self . raw = copy . deepcopy ( raw )
self . calls = []
self . patches = []
self . pending = 0
self . fail_patch = False
self . unknown_patch = False
self . fail_check = False
def __call__ ( self , request ):
self . calls . append (( request . method , request . url . path ))
if request . url . path == "/authentication" :
return httpx . Response ( 201 , json = {})
if request . method == "PATCH" and request . url . path == "/alphas/alpha1" :
self . patches . append ( json . loads ( request . content ))
if self . fail_patch :
return httpx . Response ( 400 , json = { "secret" : "not-for-client" })
for section , value in self . patches [ - 1 ] . items ():
self . raw [ section ] . update ( value )
if self . unknown_patch :
self . unknown_patch = False
raise httpx . ReadTimeout ( "secret" , request = request )
return httpx . Response ( 204 )
if request . method == "GET" and request . url . path == "/alphas/alpha1" :
return httpx . Response ( 200 , json = self . raw )
if request . method == "GET" and request . url . path == "/alphas/alpha1/check" :
if self . fail_check :
return httpx . Response ( 403 , json = {})
if self . pending :
self . pending -= 1
return httpx . Response ( 202 , headers = { "Retry-After" : "0" }, json = {})
return httpx . Response ( 200 , json = { "is" : { "checks" : CHECKS }})
raise AssertionError ( f "Unexpected platform operation { request . method } { request . url . path } " )
async def setup ( app , raw = None ):
raw = raw or alpha ()
async with app . state . sessions . begin () as db :
await upsert_alpha ( db , raw )
account = await db . get ( Account , 1 )
account . email = "test@example.com"
account . password_encrypted = cipher ( app . state . settings ) . encrypt ( b "fake-password" ) . decode ()
account . connection_status = "connected"
db . add ( SelfCorrelation ( alpha_id = "alpha1" , region = "USA" , result = { "status" : "low" }, stale = False ))
( await db . get ( Research , "alpha1" )) . note = "preserve local research"
platform = Platform ( raw )
await app . state . runner . client . close ()
app . state . runner . client = WqClient ( app . state . settings , transport = httpx . MockTransport ( platform ))
return platform
async def enqueue ( client , descriptions = None ):
state = ( await client . get ( "/api/v1/alphas/alpha1/submission" )) . json ()
return await client . post (
"/api/v1/alphas/alpha1/submission-check" ,
json = {
"snapshot" : state [ "snapshot" ],
"descriptions" : descriptions or { "regular" : FIELDS },
},
)
@pytest.mark.parametrize ( "kind" , [ "REGULAR" , "SUPER" ])
async def test_write_check_poll_and_preserve_snapshot ( app , logged_in , kind ):
raw = (
alpha ()
if kind == "REGULAR"
else alpha ( type = "SUPER" , selection = { "code" : "rank(close)" }, combo = { "code" : "alpha" })
)
platform = await setup ( app , raw )
platform . pending = 2
descriptions = { key : FIELDS for key in ([ "regular" ] if kind == "REGULAR" else [ "selection" , "combo" ])}
response = await enqueue ( logged_in , descriptions )
assert response . status_code == 202 , response . text
job_id = response . json ()[ "id" ]
duplicate = await enqueue ( logged_in , descriptions )
assert duplicate . json ()[ "id" ] == job_id
await app . state . runner . execute ( job_id )
async with app . state . sessions () as db :
job = await db . get ( Job , job_id )
assert job . status == "completed" , job . error
assert job . processed == 1 and job . checkpoint [ "phase" ] == "checked"
item = await db . get ( Alpha , "alpha1" )
assert item . check_type == "FAIL_1" and item . prod_correlation == 0.8
assert item . checks == CHECKS and item . is_metrics [ "checks" ] == CHECKS
assert item . expression == raw [ "regular" ][ "code" ] and item . sharpe == 1.5
assert item . raw [ "settings" ] == raw [ "settings" ]
assert ( await db . get ( Research , "alpha1" )) . note == "preserve local research"
assert not ( await db . get ( SelfCorrelation , "alpha1" )) . stale
assert platform . patches == [{ key : { "description" : Description ( ** FIELDS ) . text ()} for key in descriptions }]
assert platform . calls . count (( "GET" , "/alphas/alpha1/check" )) == 3
assert not any ( path . endswith ( "/submit" ) for _ , path in platform . calls )
# Durable completion does not repeat either operation.
await app . state . runner . execute ( job_id )
assert len ( platform . patches ) == 1
state = ( await logged_in . get ( "/api/v1/alphas/alpha1/submission" )) . json ()
assert state [ "job" ][ "status" ] == "completed"
@pytest.mark.parametrize (
"status,stale" , [( "high" , False ), ( "partial" , False ), ( "insufficient_data" , False ), ( "low" , True )]
)
async def test_local_correlation_blocks_upstream ( app , logged_in , status , stale ):
platform = await setup ( app )
async with app . state . sessions . begin () as db :
row = await db . get ( SelfCorrelation , "alpha1" )
row . result , row . stale = { "status" : status }, stale
response = await enqueue ( logged_in )
assert response . status_code == 409
assert platform . calls == []
2026-09-10 16:36:35 +08:00
@pytest.mark.parametrize ( "kind" , [ "REGULAR" , "SUPER" ])
@pytest.mark.parametrize ( "refresh" , [ False , True ])
@pytest.mark.parametrize (
"before,after" ,
[
( "" , "Idea: \n Rationale for data used: \n Rationale for operators used:" ),
( "Idea: \r\n Rationale for data used: \t Rationale for operators used: " , "" ),
( None , " \r\n\t\u00a0 " ),
],
)
async def test_empty_description_representations_allow_check ( app , logged_in , kind , refresh , before , after ):
sections = [ "regular" ] if kind == "REGULAR" else [ "selection" , "combo" ]
raw = alpha ( type = kind , ** { key : { "code" : "rank(close)" , "description" : before } for key in sections })
platform = await setup ( app , raw )
state = ( await logged_in . get ( "/api/v1/alphas/alpha1/submission" )) . json ()
for key in sections :
platform . raw [ key ][ "description" ] = after
if refresh :
async with app . state . sessions . begin () as db :
await upsert_alpha ( db , platform . raw )
target = Description ( ** FIELDS ) . text ()
response = await logged_in . post (
"/api/v1/alphas/alpha1/submission-check" ,
json = { "snapshot" : state [ "snapshot" ], "descriptions" : dict . fromkeys ( sections , target )},
)
assert response . status_code == 202 , response . text
await app . state . runner . execute ( response . json ()[ "id" ])
async with app . state . sessions () as db :
job = await db . get ( Job , response . json ()[ "id" ])
assert job . status == "completed" , job . error
assert platform . patches == [{ key : { "description" : target } for key in sections }]
assert ( "GET" , "/alphas/alpha1/check" ) in platform . calls
2026-09-09 20:24:47 +08:00
@pytest.mark.parametrize ( "change" , [ "description" , "code" , "settings" , "status" ])
async def test_remote_conflict_never_overwrites ( app , logged_in , change ):
platform = await setup ( app )
response = await enqueue ( logged_in )
if change in ( "description" , "code" ):
platform . raw [ "regular" ][ change ] = "changed by another user"
elif change == "settings" :
platform . raw [ "settings" ][ "delay" ] = 0
else :
platform . raw [ "status" ] = "ACTIVE"
await app . state . runner . execute ( response . json ()[ "id" ])
async with app . state . sessions () as db :
assert ( await db . get ( Job , response . json ()[ "id" ])) . status == "failed"
assert platform . patches == []
assert ( "GET" , "/alphas/alpha1/check" ) not in platform . calls
@pytest.mark.parametrize ( "failure" , [ "fail_patch" , "unknown_patch" , "fail_check" ])
async def test_retry_reconciles_partial_writes ( app , logged_in , failure ):
platform = await setup ( app )
setattr ( platform , failure , True )
response = await enqueue ( logged_in )
job_id = response . json ()[ "id" ]
await app . state . runner . execute ( job_id )
async with app . state . sessions () as db :
assert ( await db . get ( Job , job_id )) . status == "failed"
item = await db . get ( Alpha , "alpha1" )
assert item . checks != CHECKS
if failure == "fail_check" :
assert item . raw [ "regular" ][ "description" ] == Description ( ** FIELDS ) . text ()
if failure == "fail_patch" :
assert ( "GET" , "/alphas/alpha1/check" ) not in platform . calls
setattr ( platform , failure , False )
await app . state . runner . execute ( job_id )
async with app . state . sessions () as db :
assert ( await db . get ( Job , job_id )) . status == "completed"
assert len ( platform . patches ) == ( 2 if failure == "fail_patch" else 1 )
async def test_complete_description_reused_verbatim ( app , logged_in ):
original = Description ( ** FIELDS ) . text () . replace ( " \n " , " \n\n " )
platform = await setup ( app , alpha ( regular = { "code" : "rank(close)" , "description" : original }))
response = await enqueue ( logged_in )
await app . state . runner . execute ( response . json ()[ "id" ])
assert not platform . patches
assert ( "GET" , "/alphas/alpha1/check" ) in platform . calls
2026-09-10 15:32:59 +08:00
@pytest.mark.parametrize ( "kind" , [ "REGULAR" , "SUPER" ])
2026-09-10 16:07:18 +08:00
@pytest.mark.parametrize (
"text" ,
[
"Brief draft" ,
" \n " . join ( HEADINGS ),
Description ( ** ACCRUAL_FIELDS ) . text (),
" An idea. \n\n More detail. " ,
],
ids = [ "short_plain_text" , "empty_template" , "recorded_over_500" , "whitespace" ],
)
async def test_generated_text_is_returned_verbatim_without_content_validation ( app , logged_in , kind , text ):
2026-09-10 15:32:59 +08:00
raw = (
alpha ()
if kind == "REGULAR"
else alpha ( type = "SUPER" , selection = { "code" : "rank(close)" }, combo = { "code" : "alpha" })
)
platform = await setup ( app , raw )
sections = [ "regular" ] if kind == "REGULAR" else [ "selection" , "combo" ]
2026-09-09 20:24:47 +08:00
seen = []
2026-09-10 16:07:18 +08:00
prompts = []
def answer ( messages , info ):
assert not info . output_tools and not info . function_tools
prompts . append (
json . loads ( next ( p . content for m in messages for p in m . parts if isinstance ( p , UserPromptPart )))
)
return ModelResponse ( parts = [ TextPart ( text + prompts [ - 1 ][ "section" ])])
2026-09-09 20:24:47 +08:00
@asynccontextmanager
async def model_factory ( config , settings ):
seen . append (
(
config . base_url ,
config . model ,
config . protocol ,
cipher ( settings ) . decrypt ( config . api_key_encrypted . encode ()) . decode (),
)
)
2026-09-10 16:07:18 +08:00
yield FunctionModel ( function = answer )
2026-09-09 20:24:47 +08:00
app . state . ai . model_factory = model_factory
response = await logged_in . put (
"/api/v1/ai/settings" ,
json = {
"base_url" : "https://model.test/v1" ,
"model" : "bot-model" ,
"description_model" : "description-model" ,
"protocol" : "responses" ,
"api_key" : "shared-secret" ,
},
)
assert response . status_code == 200 and "shared-secret" not in response . text
state = ( await logged_in . get ( "/api/v1/alphas/alpha1/submission" )) . json ()
generated = await logged_in . post (
"/api/v1/alphas/alpha1/description/generate" , json = { "snapshot" : state [ "snapshot" ]}
)
assert generated . status_code == 200 , generated . text
2026-09-10 16:07:18 +08:00
assert generated . json ()[ "descriptions" ] == { key : text + key for key in sections }
2026-09-09 20:24:47 +08:00
assert seen == [( "https://model.test/v1" , "description-model" , "responses" , "shared-secret" )]
2026-09-10 16:07:18 +08:00
assert [ prompt [ "section" ] for prompt in prompts ] == sections
assert [ prompt [ "code" ] for prompt in prompts ] == [ raw [ key ][ "code" ] for key in sections ]
2026-09-09 20:24:47 +08:00
assert not platform . calls
async with app . state . sessions () as db :
assert ( await db . get ( AISettings , 1 )) . model == "bot-model"
assert "description" not in ( await db . get ( Alpha , "alpha1" )) . raw [ "regular" ]
2026-09-10 16:07:18 +08:00
async def test_generation_requires_config_and_safely_reports_provider_failure ( app , logged_in , caplog ):
platform = await setup ( app )
2026-09-09 20:24:47 +08:00
state = ( await logged_in . get ( "/api/v1/alphas/alpha1/submission" )) . json ()
path = "/api/v1/alphas/alpha1/description/generate"
assert ( await logged_in . post ( path , json = { "snapshot" : state [ "snapshot" ]})) . status_code == 409
await logged_in . put (
"/api/v1/ai/settings" ,
json = {
"base_url" : "https://model.test/v1" ,
"model" : "bot" ,
"description_model" : "description" ,
"api_key" : "shared-secret" ,
},
)
2026-09-10 16:07:18 +08:00
def fail ( messages , info ):
raise httpx . ReadTimeout ( "shared-secret private provider response" )
2026-09-09 20:24:47 +08:00
@asynccontextmanager
async def broken ( config , settings ):
2026-09-10 16:07:18 +08:00
yield FunctionModel ( function = fail )
2026-09-09 20:24:47 +08:00
app . state . ai . model_factory = broken
response = await logged_in . post ( path , json = { "snapshot" : state [ "snapshot" ]})
2026-09-10 16:07:18 +08:00
assert response . status_code == 502
assert "超时" in response . json ()[ "detail" ]
assert "ReadTimeout" in caplog . text
2026-09-10 15:32:59 +08:00
assert "shared-secret" not in response . text + caplog . text
2026-09-10 16:07:18 +08:00
assert "private provider response" not in response . text + caplog . text
2026-09-10 15:32:59 +08:00
assert not platform . calls
@pytest.mark.parametrize ( "protocol" , [ "chat_completions" , "responses" ])
2026-09-10 16:07:18 +08:00
@pytest.mark.parametrize ( "returned_fields" , [ FIELDS , ACCRUAL_FIELDS ], ids = [ "short" , "recorded_over_500" ])
async def test_description_text_output_through_real_provider ( app , logged_in , protocol , returned_fields ):
2026-09-10 15:32:59 +08:00
from app.ai.provider import model_connection
platform = await setup ( app )
await logged_in . put (
"/api/v1/ai/settings" ,
json = {
"base_url" : "https://model.test/v1" ,
"model" : "bot" ,
"description_model" : "description-wire-model" ,
"protocol" : protocol ,
"api_key" : "synthetic-key" ,
},
)
paths = []
def gateway ( request ):
paths . append ( request . url . path )
body = json . loads ( request . content )
assert request . headers [ "authorization" ] == "Bearer synthetic-key"
assert body [ "model" ] == "description-wire-model" and body [ "stream" ] is False
2026-09-10 16:07:18 +08:00
assert not body . get ( "tools" ) and not body . get ( "response_format" )
text = Description ( ** returned_fields ) . text ()
2026-09-10 15:32:59 +08:00
if protocol == "chat_completions" :
return httpx . Response (
200 ,
json = {
"id" : "chat-description" ,
"object" : "chat.completion" ,
"created" : 1789000000 ,
"model" : body [ "model" ],
"choices" : [
{
"index" : 0 ,
2026-09-10 16:07:18 +08:00
"finish_reason" : "stop" ,
2026-09-10 15:32:59 +08:00
"message" : {
"role" : "assistant" ,
2026-09-10 16:07:18 +08:00
"content" : text ,
2026-09-10 15:32:59 +08:00
},
}
],
},
)
return httpx . Response (
200 ,
json = {
"id" : "resp-description" ,
"object" : "response" ,
"created_at" : 1789000000 ,
"model" : body [ "model" ],
"status" : "completed" ,
"output" : [
{
2026-09-10 16:07:18 +08:00
"id" : "msg-description" ,
"type" : "message" ,
"role" : "assistant" ,
"content" : [{ "type" : "output_text" , "text" : text , "annotations" : []}],
2026-09-10 15:32:59 +08:00
"status" : "completed" ,
}
],
},
)
@asynccontextmanager
async def model_factory ( config , settings ):
async with model_connection ( config , settings , httpx . MockTransport ( gateway )) as model :
yield model
app . state . ai . model_factory = model_factory
state = ( await logged_in . get ( "/api/v1/alphas/alpha1/submission" )) . json ()
response = await logged_in . post (
"/api/v1/alphas/alpha1/description/generate" , json = { "snapshot" : state [ "snapshot" ]}
)
assert response . status_code == 200 , response . text
2026-09-10 16:07:18 +08:00
assert response . json ()[ "descriptions" ] == { "regular" : Description ( ** returned_fields ) . text ()}
2026-09-10 15:32:59 +08:00
assert paths == [ "/v1/" + ( "chat/completions" if protocol == "chat_completions" else "responses" )]
assert not platform . calls
2026-09-09 20:24:47 +08:00
2026-09-10 16:07:18 +08:00
@pytest.mark.parametrize ( "format" , [ "fields" , "complete_text" ])
async def test_manual_description_over_500_is_written_completely ( app , logged_in , format ):
platform = await setup ( app )
fields = { ** FIELDS , "idea" : "x" * 600 }
text = " \n\n " . join ( heading + value for heading , value in zip ( HEADINGS , fields . values ()))
response = await enqueue ( logged_in , { "regular" : fields if format == "fields" else text })
assert response . status_code == 202 , response . text
await app . state . runner . execute ( response . json ()[ "id" ])
assert platform . patches == [{ "regular" : { "description" : text }}]
2026-09-09 20:24:47 +08:00
async def test_invalid_snapshot_and_section_rejected ( app , logged_in ):
platform = await setup ( app )
response = await logged_in . post (
"/api/v1/alphas/alpha1/submission-check" ,
json = {
"snapshot" : "0" * 64 ,
"descriptions" : { "regular" : FIELDS },
},
)
assert response . status_code == 409
assert ( await enqueue ( logged_in , { "combo" : FIELDS })) . status_code == 422
assert platform . calls == []
async def test_description_model_does_not_invalidate_bot_test ( app , logged_in ):
from tests.test_ai import CONFIG , configure
await configure ( app , logged_in )
response = await logged_in . put (
"/api/v1/ai/settings" ,
json = {
** { k : v for k , v in CONFIG . items () if k != "api_key" },
"enabled" : True ,
"description_model" : " separate-description-model " ,
},
)
assert response . json ()[ "ready" ] and response . json ()[ "enabled" ]
assert response . json ()[ "description_model" ] == "separate-description-model"
# Older clients saving bot settings do not clear the independently configured model.
response = await logged_in . put (
"/api/v1/ai/settings" ,
json = {
** { k : v for k , v in CONFIG . items () if k != "api_key" },
"enabled" : True ,
},
)
assert response . json ()[ "description_model" ] == "separate-description-model"
def test_description_model_migration_preserves_existing_config ( tmp_path ):
import importlib.util
from pathlib import Path
import sqlalchemy as sa
from alembic.migration import MigrationContext
from alembic.operations import Operations
path = Path ( __file__ ) . parents [ 1 ] / "migrations/versions/0012_description_model.py"
spec = importlib . util . spec_from_file_location ( "description_migration" , path )
migration = importlib . util . module_from_spec ( spec )
spec . loader . exec_module ( migration )
engine = sa . create_engine ( f "sqlite:/// { tmp_path } /migration.db" )
with engine . begin () as connection :
connection . exec_driver_sql ( "CREATE TABLE ai_settings (id INTEGER PRIMARY KEY, model VARCHAR(200))" )
connection . exec_driver_sql ( "INSERT INTO ai_settings VALUES (1, 'keep-bot-model')" )
with Operations . context ( MigrationContext . configure ( connection )):
migration . upgrade ()
assert connection . exec_driver_sql ( "SELECT model, description_model FROM ai_settings" ) . one () == (
"keep-bot-model" ,
"" ,
)
migration . downgrade ()
assert connection . exec_driver_sql ( "SELECT model FROM ai_settings" ) . scalar () == "keep-bot-model"
assert "description_model" not in {
c [ "name" ] for c in sa . inspect ( connection ) . get_columns ( "ai_settings" )
}
engine . dispose ()
2026-09-10 11:45:05 +08:00
@pytest.mark.parametrize ( "cached" , [ False , True ])
async def test_no_local_references_does_not_block_platform_check ( app , logged_in , cached ):
platform = await setup ( app )
async with app . state . sessions . begin () as db :
result = await db . get ( SelfCorrelation , "alpha1" )
if cached :
result . result = { "status" : "insufficient_data" , "candidate_count" : 0 }
else :
await db . delete ( result )
state = ( await logged_in . get ( "/api/v1/alphas/alpha1/submission" )) . json ()
assert state [ "can_check" ] is True
response = await enqueue ( logged_in )
assert response . status_code == 202 , response . text
await app . state . runner . execute ( response . json ()[ "id" ])
async with app . state . sessions () as db :
job = await db . get ( Job , response . json ()[ "id" ])
assert job . status == "completed" , job . error
assert ( "GET" , "/alphas/alpha1/check" ) in platform . calls
assert not any ( path . endswith ( "/submit" ) for _ , path in platform . calls )
async def test_new_reference_blocks_empty_cached_result_at_enqueue_and_execution ( app , logged_in ):
platform = await setup ( app )
async with app . state . sessions . begin () as db :
( await db . get ( SelfCorrelation , "alpha1" )) . result = {
"status" : "insufficient_data" ,
"candidate_count" : 0 ,
}
response = await enqueue ( logged_in )
assert response . status_code == 202
async with app . state . sessions . begin () as db :
await upsert_alpha ( db , alpha ( id = "peer" , status = "ACTIVE" ))
assert not ( await logged_in . get ( "/api/v1/alphas/alpha1/submission" )) . json ()[ "can_check" ]
assert ( await enqueue ( logged_in )) . status_code == 409
await app . state . runner . execute ( response . json ()[ "id" ])
async with app . state . sessions () as db :
assert ( await db . get ( Job , response . json ()[ "id" ])) . status == "failed"
assert not platform . patches
assert ( "GET" , "/alphas/alpha1/check" ) not in platform . calls
async def test_complete_text_input_is_written_as_three_paragraphs ( app , logged_in ):
platform = await setup ( app )
text = Description ( ** FIELDS ) . text ()
response = await enqueue ( logged_in , { "regular" : text })
assert response . status_code == 202
await app . state . runner . execute ( response . json ()[ "id" ])
assert platform . patches == [{ "regular" : { "description" : text }}]
assert len ( text . split ( " \n\n " )) == 3
state = ( await logged_in . get ( "/api/v1/alphas/alpha1/submission" )) . json ()
assert state [ "descriptions" ] == { "regular" : text }
2026-09-10 16:07:18 +08:00
@pytest.mark.parametrize ( "heading,field" , list ( zip ( HEADINGS , FIELDS )))
@pytest.mark.parametrize ( "failure" , [ "missing_label" , "empty_content" ])
async def test_incomplete_template_can_be_reviewed_and_written ( app , logged_in , heading , field , failure ):
platform = await setup ( app )
text = Description ( ** FIELDS ) . text ()
bad = text . replace ( heading , "" ) if failure == "missing_label" else text . replace ( FIELDS [ field ], " \n " )
response = await enqueue ( logged_in , { "regular" : bad })
assert response . status_code == 202 , response . text
await app . state . runner . execute ( response . json ()[ "id" ])
assert platform . patches == [{ "regular" : { "description" : bad }}]
2026-09-10 11:45:05 +08:00
2026-09-10 16:07:18 +08:00
@pytest.mark.parametrize ( "text" , [ "" , "short" , "x" * 600 , " Idea: draft \n More details. " ])
async def test_reviewed_plain_text_is_written_verbatim ( app , logged_in , text ):
platform = await setup ( app )
response = await enqueue ( logged_in , { "regular" : text })
assert response . status_code == 202 , response . text
await app . state . runner . execute ( response . json ()[ "id" ])
assert platform . patches == ([{ "regular" : { "description" : text }}] if text else [])