@@ -1781,3 +1781,93 @@ impl Default for MockWorkerConfig {
17811781 }
17821782 }
17831783}
1784+
1785+ /// A minimal OpenAI-compatible mock worker that does not implement /server_info or /model_info.
1786+ /// Used to test fallback model name discovery via /v1/models.
1787+ pub struct OpenAiOnlyMockWorker {
1788+ port : u16 ,
1789+ model_name : String ,
1790+ shutdown_handle : Option < tokio:: task:: JoinHandle < ( ) > > ,
1791+ shutdown_tx : Option < tokio:: sync:: oneshot:: Sender < ( ) > > ,
1792+ }
1793+
1794+ impl OpenAiOnlyMockWorker {
1795+ pub fn new ( model_name : impl Into < String > ) -> Self {
1796+ Self {
1797+ port : 0 ,
1798+ model_name : model_name. into ( ) ,
1799+ shutdown_handle : None ,
1800+ shutdown_tx : None ,
1801+ }
1802+ }
1803+
1804+ pub async fn start ( & mut self ) -> Result < String , Box < dyn std:: error:: Error > > {
1805+ let listener = std:: net:: TcpListener :: bind ( "127.0.0.1:0" ) ?;
1806+ self . port = listener. local_addr ( ) ?. port ( ) ;
1807+ drop ( listener) ;
1808+
1809+ let model_name = self . model_name . clone ( ) ;
1810+ let port = self . port ;
1811+
1812+ let app = Router :: new ( )
1813+ . route ( "/health" , get ( || async { Json ( json ! ( { "status" : "healthy" } ) ) } ) )
1814+ . route ( "/health_generate" , get ( || async { Json ( json ! ( { "status" : "ok" } ) ) } ) )
1815+ . route (
1816+ "/v1/models" ,
1817+ get ( move || {
1818+ let model_name = model_name. clone ( ) ;
1819+ async move {
1820+ let ts = SystemTime :: now ( )
1821+ . duration_since ( UNIX_EPOCH )
1822+ . unwrap ( )
1823+ . as_secs ( ) ;
1824+ Json ( json ! ( {
1825+ "object" : "list" ,
1826+ "data" : [ { "id" : model_name, "object" : "model" , "created" : ts, "owned_by" : "owner" } ]
1827+ } ) )
1828+ }
1829+ } ) ,
1830+ ) ;
1831+
1832+ let ( shutdown_tx, shutdown_rx) = tokio:: sync:: oneshot:: channel :: < ( ) > ( ) ;
1833+ self . shutdown_tx = Some ( shutdown_tx) ;
1834+
1835+ let handle = tokio:: spawn ( async move {
1836+ let listener = match tokio:: net:: TcpListener :: bind ( ( "127.0.0.1" , port) ) . await {
1837+ Ok ( l) => l,
1838+ Err ( e) => {
1839+ eprintln ! ( "Failed to bind to port {}: {}" , port, e) ;
1840+ return ;
1841+ }
1842+ } ;
1843+ let server = axum:: serve ( listener, app) . with_graceful_shutdown ( async move {
1844+ let _ = shutdown_rx. await ;
1845+ } ) ;
1846+ if let Err ( e) = server. await {
1847+ eprintln ! ( "Server error: {}" , e) ;
1848+ }
1849+ } ) ;
1850+
1851+ self . shutdown_handle = Some ( handle) ;
1852+ tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 100 ) ) . await ;
1853+
1854+ Ok ( format ! ( "http://127.0.0.1:{}" , self . port) )
1855+ }
1856+
1857+ pub async fn stop ( & mut self ) {
1858+ if let Some ( tx) = self . shutdown_tx . take ( ) {
1859+ let _ = tx. send ( ( ) ) ;
1860+ }
1861+ if let Some ( h) = self . shutdown_handle . take ( ) {
1862+ let _ = tokio:: time:: timeout ( tokio:: time:: Duration :: from_secs ( 5 ) , h) . await ;
1863+ }
1864+ }
1865+ }
1866+
1867+ impl Drop for OpenAiOnlyMockWorker {
1868+ fn drop ( & mut self ) {
1869+ if let Some ( tx) = self . shutdown_tx . take ( ) {
1870+ let _ = tx. send ( ( ) ) ;
1871+ }
1872+ }
1873+ }
0 commit comments