@@ -1844,3 +1844,212 @@ func TestSyncPreferredSourceFailurePreservationWithRuntimeModels(t *testing.T) {
18441844 t .Fatalf ("expected models.dev price to be preserved, got %#v" , p )
18451845 }
18461846}
1847+
1848+ func TestRuntimeModelPricingStatus (t * testing.T ) {
1849+ st := testutil .NewStore (t , testutil .NewConfig (t ))
1850+
1851+ initialPrices := map [string ]store.ModelPrice {
1852+ "synced-model" : {
1853+ Prompt : 1.0 ,
1854+ Completion : 2.0 ,
1855+ Source : SyncSourceLiteLLM ,
1856+ },
1857+ "manual-model" : {
1858+ Prompt : 0.5 ,
1859+ Completion : 1.5 ,
1860+ Source : "manual" ,
1861+ },
1862+ }
1863+ if err := st .SaveModelPrices (context .Background (), initialPrices ); err != nil {
1864+ t .Fatalf ("save initial prices: %v" , err )
1865+ }
1866+
1867+ remoteCalled := false
1868+ remoteServer := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
1869+ remoteCalled = true
1870+ http .Error (w , "should not call remote price sources" , http .StatusInternalServerError )
1871+ }))
1872+ defer remoteServer .Close ()
1873+
1874+ cpaServer := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
1875+ switch r .URL .Path {
1876+ case "/v0/management/api-keys" :
1877+ _ = json .NewEncoder (w ).Encode (map [string ]any {
1878+ "api-keys" : []string {"secret-key-1" , "secret-key-2" },
1879+ })
1880+ case "/v1/models" :
1881+ if r .Header .Get ("Authorization" ) != "Bearer secret-key-1" {
1882+ http .Error (w , "unauthorized" , http .StatusUnauthorized )
1883+ return
1884+ }
1885+ _ = json .NewEncoder (w ).Encode (map [string ]any {
1886+ "data" : []map [string ]any {
1887+ {"id" : "zebra-model" },
1888+ {"id" : "synced-model" },
1889+ {"id" : "alpha-model" },
1890+ {"id" : "manual-model" },
1891+ {"id" : "alpha-model" },
1892+ {"id" : " " },
1893+ },
1894+ })
1895+ default :
1896+ http .NotFound (w , r )
1897+ }
1898+ }))
1899+ defer cpaServer .Close ()
1900+
1901+ resolver := staticSetupResolver {
1902+ setup : store.Setup {
1903+ CPAUpstreamURL : cpaServer .URL ,
1904+ ManagementKey : "mgmt-secret-key" ,
1905+ },
1906+ }
1907+ remoteURL := remoteServer .URL
1908+ svc := New (st , & remoteURL , resolver )
1909+
1910+ status , err := svc .RuntimeModelPricingStatus (context .Background ())
1911+ if err != nil {
1912+ t .Fatalf ("RuntimeModelPricingStatus failed: %v" , err )
1913+ }
1914+
1915+ if remoteCalled {
1916+ t .Fatal ("RuntimeModelPricingStatus must not call remote price sources" )
1917+ }
1918+
1919+ expectedModels := []string {"alpha-model" , "manual-model" , "synced-model" , "zebra-model" }
1920+ if len (status .Models ) != len (expectedModels ) {
1921+ t .Fatalf ("expected %d models, got %d: %#v" , len (expectedModels ), len (status .Models ), status .Models )
1922+ }
1923+ for i , m := range expectedModels {
1924+ if status .Models [i ] != m {
1925+ t .Fatalf ("expected model at %d to be %s, got %s" , i , m , status .Models [i ])
1926+ }
1927+ }
1928+ if status .Count != 4 {
1929+ t .Fatalf ("expected count 4, got %d" , status .Count )
1930+ }
1931+
1932+ expectedUnpriced := []string {"alpha-model" , "zebra-model" }
1933+ if len (status .UnpricedModels ) != len (expectedUnpriced ) {
1934+ t .Fatalf ("expected %d unpriced models, got %d: %#v" , len (expectedUnpriced ), len (status .UnpricedModels ), status .UnpricedModels )
1935+ }
1936+ for i , m := range expectedUnpriced {
1937+ if status .UnpricedModels [i ] != m {
1938+ t .Fatalf ("expected unpriced model at %d to be %s, got %s" , i , m , status .UnpricedModels [i ])
1939+ }
1940+ }
1941+ if status .UnpricedCount != 2 {
1942+ t .Fatalf ("expected unpricedCount 2, got %d" , status .UnpricedCount )
1943+ }
1944+
1945+ storedPrices , err := st .LoadModelPrices (context .Background ())
1946+ if err != nil {
1947+ t .Fatalf ("load prices: %v" , err )
1948+ }
1949+ if len (storedPrices ) != 2 {
1950+ t .Fatalf ("expected 2 stored prices, got %d" , len (storedPrices ))
1951+ }
1952+ }
1953+
1954+ func TestRuntimeModelPricingStatus_AllUnpricedWhenNoPrices (t * testing.T ) {
1955+ st := testutil .NewStore (t , testutil .NewConfig (t ))
1956+
1957+ cpaServer := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
1958+ switch r .URL .Path {
1959+ case "/v0/management/api-keys" :
1960+ _ = json .NewEncoder (w ).Encode (map [string ]any {
1961+ "api-keys" : []string {"test-key" },
1962+ })
1963+ case "/v1/models" :
1964+ _ = json .NewEncoder (w ).Encode (map [string ]any {
1965+ "data" : []map [string ]any {
1966+ {"id" : "m1" },
1967+ {"id" : "m2" },
1968+ },
1969+ })
1970+ default :
1971+ http .NotFound (w , r )
1972+ }
1973+ }))
1974+ defer cpaServer .Close ()
1975+
1976+ resolver := staticSetupResolver {
1977+ setup : store.Setup {
1978+ CPAUpstreamURL : cpaServer .URL ,
1979+ ManagementKey : "mgmt-key" ,
1980+ },
1981+ }
1982+ svc := New (st , nil , resolver )
1983+
1984+ status , err := svc .RuntimeModelPricingStatus (context .Background ())
1985+ if err != nil {
1986+ t .Fatalf ("RuntimeModelPricingStatus failed: %v" , err )
1987+ }
1988+ if status .Count != 2 || status .UnpricedCount != 2 {
1989+ t .Fatalf ("expected count=2 and unpricedCount=2, got count=%d, unpriced=%d" , status .Count , status .UnpricedCount )
1990+ }
1991+ if len (status .UnpricedModels ) != 2 || status .UnpricedModels [0 ] != "m1" || status .UnpricedModels [1 ] != "m2" {
1992+ t .Fatalf ("unexpected unpriced models: %#v" , status .UnpricedModels )
1993+ }
1994+ }
1995+
1996+ func TestRuntimeModelPricingStatus_ZeroModels (t * testing.T ) {
1997+ st := testutil .NewStore (t , testutil .NewConfig (t ))
1998+
1999+ cpaServer := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
2000+ switch r .URL .Path {
2001+ case "/v0/management/api-keys" :
2002+ _ = json .NewEncoder (w ).Encode (map [string ]any {
2003+ "api-keys" : []string {"test-key" },
2004+ })
2005+ case "/v1/models" :
2006+ _ = json .NewEncoder (w ).Encode (map [string ]any {
2007+ "data" : []map [string ]any {},
2008+ })
2009+ default :
2010+ http .NotFound (w , r )
2011+ }
2012+ }))
2013+ defer cpaServer .Close ()
2014+
2015+ resolver := staticSetupResolver {
2016+ setup : store.Setup {
2017+ CPAUpstreamURL : cpaServer .URL ,
2018+ ManagementKey : "mgmt-key" ,
2019+ },
2020+ }
2021+ svc := New (st , nil , resolver )
2022+
2023+ status , err := svc .RuntimeModelPricingStatus (context .Background ())
2024+ if err != nil {
2025+ t .Fatalf ("RuntimeModelPricingStatus failed: %v" , err )
2026+ }
2027+ if status .Count != 0 || status .UnpricedCount != 0 {
2028+ t .Fatalf ("expected count=0, unpricedCount=0, got %d, %d" , status .Count , status .UnpricedCount )
2029+ }
2030+ if status .Models == nil || status .UnpricedModels == nil {
2031+ t .Fatal ("expected models and unpricedModels to be non-nil empty slices" )
2032+ }
2033+ }
2034+
2035+ func TestRuntimeModelPricingStatus_DiscoveryFailure (t * testing.T ) {
2036+ st := testutil .NewStore (t , testutil .NewConfig (t ))
2037+
2038+ cpaServer := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
2039+ http .Error (w , "cpa internal error" , http .StatusInternalServerError )
2040+ }))
2041+ defer cpaServer .Close ()
2042+
2043+ resolver := staticSetupResolver {
2044+ setup : store.Setup {
2045+ CPAUpstreamURL : cpaServer .URL ,
2046+ ManagementKey : "mgmt-key" ,
2047+ },
2048+ }
2049+ svc := New (st , nil , resolver )
2050+
2051+ _ , err := svc .RuntimeModelPricingStatus (context .Background ())
2052+ if err == nil {
2053+ t .Fatal ("expected error on discovery failure, got nil" )
2054+ }
2055+ }
0 commit comments