@@ -1729,6 +1729,297 @@ describe("API", () => {
17291729 } ) ;
17301730 } ) ;
17311731
1732+ // Prompts routes (TRI-8738). Resource shapes:
1733+ // - List resource: { type: "prompts", id: "all" } action: read
1734+ // - Retrieve resource: { type: "prompts", id: params.slug } action: read
1735+ // - Override resource: { type: "prompts", id: params.slug } action: update
1736+ // (multi-method: POST/PUT/PATCH/DELETE)
1737+ // - Promote resource: { type: "prompts", id: params.slug } action: update
1738+ // - Reactivate resource: { type: "prompts", id: params.slug } action: update
1739+ //
1740+ // ACTION_ALIASES: update ← write, so write:prompts also satisfies
1741+ // the update-action routes.
1742+ //
1743+ // Auth happens before any DB lookup, so we test against
1744+ // non-existent slugs — handler will 404 but we assert "not 401/403"
1745+ // for pass cases.
1746+ describe ( "Prompts list — GET /api/v1/prompts (collection-level)" , ( ) => {
1747+ const path = "/api/v1/prompts" ;
1748+ const get = ( headers : Record < string , string > ) =>
1749+ getTestServer ( ) . webapp . fetch ( path , { headers } ) ;
1750+
1751+ it ( "missing auth: 401" , async ( ) => {
1752+ const res = await getTestServer ( ) . webapp . fetch ( path ) ;
1753+ expect ( res . status ) . toBe ( 401 ) ;
1754+ } ) ;
1755+
1756+ it ( "private API key: auth passes" , async ( ) => {
1757+ const server = getTestServer ( ) ;
1758+ const seed = await seedTestEnvironment ( server . prisma ) ;
1759+ const res = await get ( { Authorization : `Bearer ${ seed . apiKey } ` } ) ;
1760+ expect ( res . status ) . not . toBe ( 401 ) ;
1761+ expect ( res . status ) . not . toBe ( 403 ) ;
1762+ } ) ;
1763+
1764+ it ( "JWT read:prompts (type-level): auth passes" , async ( ) => {
1765+ const server = getTestServer ( ) ;
1766+ const seed = await seedTestEnvironment ( server . prisma ) ;
1767+ const jwt = await generateJWT ( {
1768+ secretKey : seed . apiKey ,
1769+ payload : { pub : true , sub : seed . environment . id , scopes : [ "read:prompts" ] } ,
1770+ expirationTime : "15m" ,
1771+ } ) ;
1772+ const res = await get ( { Authorization : `Bearer ${ jwt } ` } ) ;
1773+ expect ( res . status ) . not . toBe ( 401 ) ;
1774+ expect ( res . status ) . not . toBe ( 403 ) ;
1775+ } ) ;
1776+
1777+ it ( "JWT read:runs: 403 (type mismatch)" , async ( ) => {
1778+ const server = getTestServer ( ) ;
1779+ const seed = await seedTestEnvironment ( server . prisma ) ;
1780+ const jwt = await generateJWT ( {
1781+ secretKey : seed . apiKey ,
1782+ payload : { pub : true , sub : seed . environment . id , scopes : [ "read:runs" ] } ,
1783+ expirationTime : "15m" ,
1784+ } ) ;
1785+ const res = await get ( { Authorization : `Bearer ${ jwt } ` } ) ;
1786+ expect ( res . status ) . toBe ( 403 ) ;
1787+ } ) ;
1788+
1789+ it ( "JWT admin: auth passes" , async ( ) => {
1790+ const server = getTestServer ( ) ;
1791+ const seed = await seedTestEnvironment ( server . prisma ) ;
1792+ const jwt = await generateJWT ( {
1793+ secretKey : seed . apiKey ,
1794+ payload : { pub : true , sub : seed . environment . id , scopes : [ "admin" ] } ,
1795+ expirationTime : "15m" ,
1796+ } ) ;
1797+ const res = await get ( { Authorization : `Bearer ${ jwt } ` } ) ;
1798+ expect ( res . status ) . not . toBe ( 401 ) ;
1799+ expect ( res . status ) . not . toBe ( 403 ) ;
1800+ } ) ;
1801+ } ) ;
1802+
1803+ describe ( "Prompts retrieve — GET /api/v1/prompts/:slug (id-keyed read)" , ( ) => {
1804+ const SLUG = "test-prompt" ;
1805+ const path = `/api/v1/prompts/${ SLUG } ` ;
1806+ const get = ( headers : Record < string , string > ) =>
1807+ getTestServer ( ) . webapp . fetch ( path , { headers } ) ;
1808+
1809+ it ( "missing auth: 401" , async ( ) => {
1810+ const res = await getTestServer ( ) . webapp . fetch ( path ) ;
1811+ expect ( res . status ) . toBe ( 401 ) ;
1812+ } ) ;
1813+
1814+ it ( "private API key: auth passes" , async ( ) => {
1815+ const server = getTestServer ( ) ;
1816+ const seed = await seedTestEnvironment ( server . prisma ) ;
1817+ const res = await get ( { Authorization : `Bearer ${ seed . apiKey } ` } ) ;
1818+ expect ( res . status ) . not . toBe ( 401 ) ;
1819+ expect ( res . status ) . not . toBe ( 403 ) ;
1820+ } ) ;
1821+
1822+ it ( "JWT read:prompts (type-level): auth passes" , async ( ) => {
1823+ const server = getTestServer ( ) ;
1824+ const seed = await seedTestEnvironment ( server . prisma ) ;
1825+ const jwt = await generateJWT ( {
1826+ secretKey : seed . apiKey ,
1827+ payload : { pub : true , sub : seed . environment . id , scopes : [ "read:prompts" ] } ,
1828+ expirationTime : "15m" ,
1829+ } ) ;
1830+ const res = await get ( { Authorization : `Bearer ${ jwt } ` } ) ;
1831+ expect ( res . status ) . not . toBe ( 401 ) ;
1832+ expect ( res . status ) . not . toBe ( 403 ) ;
1833+ } ) ;
1834+
1835+ it ( "JWT read:prompts:<exact slug>: auth passes" , async ( ) => {
1836+ const server = getTestServer ( ) ;
1837+ const seed = await seedTestEnvironment ( server . prisma ) ;
1838+ const jwt = await generateJWT ( {
1839+ secretKey : seed . apiKey ,
1840+ payload : {
1841+ pub : true ,
1842+ sub : seed . environment . id ,
1843+ scopes : [ `read:prompts:${ SLUG } ` ] ,
1844+ } ,
1845+ expirationTime : "15m" ,
1846+ } ) ;
1847+ const res = await get ( { Authorization : `Bearer ${ jwt } ` } ) ;
1848+ expect ( res . status ) . not . toBe ( 401 ) ;
1849+ expect ( res . status ) . not . toBe ( 403 ) ;
1850+ } ) ;
1851+
1852+ it ( "JWT read:prompts:<other>: 403" , async ( ) => {
1853+ const server = getTestServer ( ) ;
1854+ const seed = await seedTestEnvironment ( server . prisma ) ;
1855+ const jwt = await generateJWT ( {
1856+ secretKey : seed . apiKey ,
1857+ payload : {
1858+ pub : true ,
1859+ sub : seed . environment . id ,
1860+ scopes : [ "read:prompts:some-other-slug" ] ,
1861+ } ,
1862+ expirationTime : "15m" ,
1863+ } ) ;
1864+ const res = await get ( { Authorization : `Bearer ${ jwt } ` } ) ;
1865+ expect ( res . status ) . toBe ( 403 ) ;
1866+ } ) ;
1867+
1868+ it ( "JWT read:runs: 403 (type mismatch)" , async ( ) => {
1869+ const server = getTestServer ( ) ;
1870+ const seed = await seedTestEnvironment ( server . prisma ) ;
1871+ const jwt = await generateJWT ( {
1872+ secretKey : seed . apiKey ,
1873+ payload : { pub : true , sub : seed . environment . id , scopes : [ "read:runs" ] } ,
1874+ expirationTime : "15m" ,
1875+ } ) ;
1876+ const res = await get ( { Authorization : `Bearer ${ jwt } ` } ) ;
1877+ expect ( res . status ) . toBe ( 403 ) ;
1878+ } ) ;
1879+
1880+ it ( "JWT admin: auth passes" , async ( ) => {
1881+ const server = getTestServer ( ) ;
1882+ const seed = await seedTestEnvironment ( server . prisma ) ;
1883+ const jwt = await generateJWT ( {
1884+ secretKey : seed . apiKey ,
1885+ payload : { pub : true , sub : seed . environment . id , scopes : [ "admin" ] } ,
1886+ expirationTime : "15m" ,
1887+ } ) ;
1888+ const res = await get ( { Authorization : `Bearer ${ jwt } ` } ) ;
1889+ expect ( res . status ) . not . toBe ( 401 ) ;
1890+ expect ( res . status ) . not . toBe ( 403 ) ;
1891+ } ) ;
1892+ } ) ;
1893+
1894+ describe ( "Prompts override — POST /api/v1/prompts/:slug/override (update action)" , ( ) => {
1895+ const SLUG = "test-prompt" ;
1896+ const path = `/api/v1/prompts/${ SLUG } /override` ;
1897+ const post = ( headers : Record < string , string > ) =>
1898+ getTestServer ( ) . webapp . fetch ( path , {
1899+ method : "POST" ,
1900+ headers : { "Content-Type" : "application/json" , ...headers } ,
1901+ body : JSON . stringify ( { content : "test" } ) ,
1902+ } ) ;
1903+
1904+ it ( "missing auth: 401" , async ( ) => {
1905+ const res = await post ( { } ) ;
1906+ expect ( res . status ) . toBe ( 401 ) ;
1907+ } ) ;
1908+
1909+ it ( "JWT write:prompts:<slug> matching (ACTION_ALIASES write→update): passes" , async ( ) => {
1910+ const server = getTestServer ( ) ;
1911+ const seed = await seedTestEnvironment ( server . prisma ) ;
1912+ const jwt = await generateJWT ( {
1913+ secretKey : seed . apiKey ,
1914+ payload : {
1915+ pub : true ,
1916+ sub : seed . environment . id ,
1917+ scopes : [ `write:prompts:${ SLUG } ` ] ,
1918+ } ,
1919+ expirationTime : "15m" ,
1920+ } ) ;
1921+ const res = await post ( { Authorization : `Bearer ${ jwt } ` } ) ;
1922+ expect ( res . status ) . not . toBe ( 401 ) ;
1923+ expect ( res . status ) . not . toBe ( 403 ) ;
1924+ } ) ;
1925+
1926+ it ( "JWT write:prompts (type-level): passes" , async ( ) => {
1927+ const server = getTestServer ( ) ;
1928+ const seed = await seedTestEnvironment ( server . prisma ) ;
1929+ const jwt = await generateJWT ( {
1930+ secretKey : seed . apiKey ,
1931+ payload : { pub : true , sub : seed . environment . id , scopes : [ "write:prompts" ] } ,
1932+ expirationTime : "15m" ,
1933+ } ) ;
1934+ const res = await post ( { Authorization : `Bearer ${ jwt } ` } ) ;
1935+ expect ( res . status ) . not . toBe ( 401 ) ;
1936+ expect ( res . status ) . not . toBe ( 403 ) ;
1937+ } ) ;
1938+
1939+ it ( "JWT read:prompts: 403 (action mismatch — read NOT aliased to update)" , async ( ) => {
1940+ const server = getTestServer ( ) ;
1941+ const seed = await seedTestEnvironment ( server . prisma ) ;
1942+ const jwt = await generateJWT ( {
1943+ secretKey : seed . apiKey ,
1944+ payload : {
1945+ pub : true ,
1946+ sub : seed . environment . id ,
1947+ scopes : [ `read:prompts:${ SLUG } ` ] ,
1948+ } ,
1949+ expirationTime : "15m" ,
1950+ } ) ;
1951+ const res = await post ( { Authorization : `Bearer ${ jwt } ` } ) ;
1952+ expect ( res . status ) . toBe ( 403 ) ;
1953+ } ) ;
1954+
1955+ it ( "JWT write:prompts:<other>: 403" , async ( ) => {
1956+ const server = getTestServer ( ) ;
1957+ const seed = await seedTestEnvironment ( server . prisma ) ;
1958+ const jwt = await generateJWT ( {
1959+ secretKey : seed . apiKey ,
1960+ payload : {
1961+ pub : true ,
1962+ sub : seed . environment . id ,
1963+ scopes : [ "write:prompts:some-other-slug" ] ,
1964+ } ,
1965+ expirationTime : "15m" ,
1966+ } ) ;
1967+ const res = await post ( { Authorization : `Bearer ${ jwt } ` } ) ;
1968+ expect ( res . status ) . toBe ( 403 ) ;
1969+ } ) ;
1970+
1971+ it ( "JWT admin: passes" , async ( ) => {
1972+ const server = getTestServer ( ) ;
1973+ const seed = await seedTestEnvironment ( server . prisma ) ;
1974+ const jwt = await generateJWT ( {
1975+ secretKey : seed . apiKey ,
1976+ payload : { pub : true , sub : seed . environment . id , scopes : [ "admin" ] } ,
1977+ expirationTime : "15m" ,
1978+ } ) ;
1979+ const res = await post ( { Authorization : `Bearer ${ jwt } ` } ) ;
1980+ expect ( res . status ) . not . toBe ( 401 ) ;
1981+ expect ( res . status ) . not . toBe ( 403 ) ;
1982+ } ) ;
1983+ } ) ;
1984+
1985+ describe ( "Prompts promote/reactivate (sanity, update action)" , ( ) => {
1986+ it ( "promote: JWT write:prompts (type-level): auth passes" , async ( ) => {
1987+ const server = getTestServer ( ) ;
1988+ const seed = await seedTestEnvironment ( server . prisma ) ;
1989+ const jwt = await generateJWT ( {
1990+ secretKey : seed . apiKey ,
1991+ payload : { pub : true , sub : seed . environment . id , scopes : [ "write:prompts" ] } ,
1992+ expirationTime : "15m" ,
1993+ } ) ;
1994+ const res = await server . webapp . fetch ( "/api/v1/prompts/some-slug/promote" , {
1995+ method : "POST" ,
1996+ headers : { "Content-Type" : "application/json" , Authorization : `Bearer ${ jwt } ` } ,
1997+ body : JSON . stringify ( { } ) ,
1998+ } ) ;
1999+ expect ( res . status ) . not . toBe ( 401 ) ;
2000+ expect ( res . status ) . not . toBe ( 403 ) ;
2001+ } ) ;
2002+
2003+ it ( "reactivate: JWT read:prompts: 403 (action mismatch)" , async ( ) => {
2004+ const server = getTestServer ( ) ;
2005+ const seed = await seedTestEnvironment ( server . prisma ) ;
2006+ const jwt = await generateJWT ( {
2007+ secretKey : seed . apiKey ,
2008+ payload : { pub : true , sub : seed . environment . id , scopes : [ "read:prompts" ] } ,
2009+ expirationTime : "15m" ,
2010+ } ) ;
2011+ const res = await server . webapp . fetch (
2012+ "/api/v1/prompts/some-slug/override/reactivate" ,
2013+ {
2014+ method : "POST" ,
2015+ headers : { "Content-Type" : "application/json" , Authorization : `Bearer ${ jwt } ` } ,
2016+ body : JSON . stringify ( { } ) ,
2017+ }
2018+ ) ;
2019+ expect ( res . status ) . toBe ( 403 ) ;
2020+ } ) ;
2021+ } ) ;
2022+
17322023 describe ( "Batch retrieve — GET /realtime/v1/batches/:batchId (sanity)" , ( ) => {
17332024 it ( "missing auth: 401" , async ( ) => {
17342025 const res = await getTestServer ( ) . webapp . fetch ( "/realtime/v1/batches/batch_anything" ) ;
0 commit comments