@@ -208,6 +208,58 @@ if (errors.length === 0) {
208208 * rolü platform araçlarını da yüklüyor ve onların uçları burada
209209 * mocklanmadığı için panel hata durumuna düşerdi. Ölçtüğümüz şey
210210 * ajan listesi, o yüzden sade hesap yeterli. */
211+ /* Platform yöneticisi. Yedek listesi bilerek karışık: otuz dört özdeş
212+ * satırın içinde tek bir başarısızlık, kartın çözmesi gereken asıl
213+ * problemdi. */
214+ if ( request . headers . cookie ?. includes ( 'orbit-platform-test=1' ) ) {
215+ const platformJson = ( body ) => {
216+ response . writeHead ( 200 , { 'cache-control' : 'no-store' , 'content-type' : 'application/json; charset=utf-8' } ) ;
217+ response . end ( JSON . stringify ( body ) ) ;
218+ } ;
219+ if ( pathname === '/v1/me' ) {
220+ platformJson ( {
221+ account : {
222+ roles : [ 'platform_owner' ] , agentQuota : - 1 , displayName : 'Orbit Owner' ,
223+ handle : 'orbit-owner' , avatarUrl : null , announcementEmails : true ,
224+ } ,
225+ session : { id : 'session-owner' } ,
226+ agentQuota : { limit : - 1 , used : 0 , remaining : - 1 , canCreate : true } ,
227+ sponsoredAgents : [ ] ,
228+ } ) ;
229+ return ;
230+ }
231+ if ( pathname === '/v1/approvals' ) { platformJson ( { reviews : [ ] } ) ; return ; }
232+ if ( pathname === '/v1/admin/announcements' ) {
233+ platformJson ( { announcements : [ {
234+ id : 'ann-1' , title : 'Bakım penceresi' , severity : 'critical' ,
235+ audienceType : 'all_agents' , status : 'active' ,
236+ } ] } ) ;
237+ return ;
238+ }
239+ if ( pathname === '/v1/admin/announcements/email-budget' ) {
240+ platformJson ( { emailBudget : { recipients : 3 , recipientCap : 60 , remainingToday : 90 , dailyBudget : 90 } } ) ;
241+ return ;
242+ }
243+ if ( pathname === '/v1/admin/media-transform-usage' ) {
244+ platformJson ( { usage : {
245+ monthUtc : '2026-08' , attemptedCount : 1 , safetyLimit : 4500 ,
246+ succeededCount : 1 , failedCount : 0 , alert : false ,
247+ } } ) ;
248+ return ;
249+ }
250+ if ( pathname === '/v1/admin/backups' ) {
251+ const runs = Array . from ( { length : 33 } , ( _ , index ) => ( {
252+ backupKind : 'daily' , status : 'succeeded' ,
253+ startedAt : Date . UTC ( 2026 , 7 , 14 - index , 6 , 17 ) , errorCode : null ,
254+ } ) ) ;
255+ runs . splice ( 4 , 0 , {
256+ backupKind : 'weekly' , status : 'failed' ,
257+ startedAt : Date . UTC ( 2026 , 7 , 9 , 6 , 17 ) , errorCode : 'r2_upload_timeout' ,
258+ } ) ;
259+ platformJson ( { backups : runs } ) ;
260+ return ;
261+ }
262+ }
211263 if ( request . headers . cookie ?. includes ( 'orbit-sponsor-test=1' ) ) {
212264 const sponsorJson = ( body ) => {
213265 response . writeHead ( 200 , { 'cache-control' : 'no-store' , 'content-type' : 'application/json; charset=utf-8' } ) ;
@@ -226,6 +278,13 @@ if (errors.length === 0) {
226278 return ;
227279 }
228280 if ( pathname === '/v1/sessions' ) { sponsorJson ( { sessions : [ ] } ) ; return ; }
281+ /* Sponsorun platform yetkisi yok: sayfa "kapalı" hâlini göstermeli
282+ * ve hiçbir yönetici ucuna dokunmamalı. */
283+ if ( pathname . startsWith ( '/v1/admin/' ) || pathname === '/v1/approvals' ) {
284+ response . writeHead ( 403 , { 'content-type' : 'application/json; charset=utf-8' } ) ;
285+ response . end ( JSON . stringify ( { error : { code : 'forbidden' , message : 'no' } } ) ) ;
286+ return ;
287+ }
229288 if ( pathname === '/v1/mcp/authorizations' ) { sponsorJson ( { authorizations : [ ] } ) ; return ; }
230289 if ( pathname === '/v1/me/connected-sites' ) { sponsorJson ( { connectedSites : [ ] } ) ; return ; }
231290 const manage = / ^ \/ v 1 \/ a g e n t s \/ ( [ ^ / ] + ) \/ m a n a g e $ / u. exec ( pathname ) ;
@@ -844,6 +903,7 @@ if (errors.length === 0) {
844903 underlinedButtons : [ ...document . querySelectorAll ( 'a.dashboard-button' ) ]
845904 . filter ( ( a ) => getComputedStyle ( a ) . textDecorationLine . includes ( 'underline' ) ) . length ,
846905 deadCards : document . querySelectorAll ( '#messages-card, #following-card, #agent-detail' ) . length ,
906+ platformLinkHidden : document . querySelector ( '#platform-link' ) ?. classList . contains ( 'hidden' ) ,
847907 } ;
848908 } ) ;
849909
@@ -875,6 +935,10 @@ if (errors.length === 0) {
875935 ) ;
876936 check ( panel . deadCards === 0 , `${ label } : kaldırılan mesaj/takip kartları ya da eski detay kabı hâlâ basılıyor.` ) ;
877937 check ( panel . underlinedButtons === 0 , `${ label } : bağlantı olarak çizilen düğmenin metni altı çizili (${ panel . underlinedButtons } ).` ) ;
938+ /* Sponsorun platform yetkisi yok; bağlantı gizli olmalı. Gizlemek
939+ * yetki kaldırmıyor — uçlar sunucuda denetleniyor — ama çalışmayan
940+ * bir kapıyı göstermenin de anlamı yok. */
941+ check ( panel . platformLinkHidden === true , `${ label } : yetkisi olmayan hesaba platform bağlantısı gösteriliyor.` ) ;
878942
879943 /* İkinci ajana geçiş: detay taşınmalı, çoğalmamalı. */
880944 await page . locator ( '.agent-row' ) . nth ( 1 ) . locator ( '.agent-list-item' ) . click ( ) ;
@@ -899,6 +963,107 @@ if (errors.length === 0) {
899963 await context . close ( ) ;
900964 }
901965
966+ /* Platform araçları sayfası. Dört ayrı iş bir dönem kişisel hesap
967+ * ayarlarının altında bir akordeonun içindeydi. */
968+ {
969+ const context = await browser . newContext ( { viewport : { width : 1440 , height : 900 } , colorScheme : 'light' } ) ;
970+ await context . addCookies ( [ { name : 'orbit-platform-test' , value : '1' , url : baseUrl } ] ) ;
971+ const page = await context . newPage ( ) ;
972+ const pageErrors = [ ] ;
973+ page . on ( 'pageerror' , ( error ) => pageErrors . push ( error . message ) ) ;
974+ await page . goto ( `${ baseUrl } /dashboard/platform` , { waitUntil : 'load' } ) ;
975+ await page . waitForSelector ( '#platform:not(.hidden)' ) ;
976+ await page . waitForSelector ( '.backup-summary' ) ;
977+
978+ const platform = await page . evaluate ( ( ) => {
979+ const box = ( selector ) => {
980+ const element = document . querySelector ( selector ) ;
981+ if ( ! element ) return null ;
982+ const rect = element . getBoundingClientRect ( ) ;
983+ return { width : Math . round ( rect . width ) , height : Math . round ( rect . height ) } ;
984+ } ;
985+ return {
986+ deniedHidden : document . querySelector ( '#platform-denied' ) ?. classList . contains ( 'hidden' ) ,
987+ announcementMeta : document . querySelector ( '#announcements .meta' ) ?. textContent ,
988+ backupSummary : document . querySelector ( '.backup-summary' ) ?. textContent ?. replace ( / \s + / gu, ' ' ) . trim ( ) ,
989+ backupFailing : document . querySelector ( '.backup-summary' ) ?. classList . contains ( 'is-failing' ) ,
990+ backupRows : document . querySelectorAll ( '#backups .dashboard-item' ) . length ,
991+ backupRowText : document . querySelector ( '#backups .dashboard-item' ) ?. textContent ?. replace ( / \s + / gu, ' ' ) . trim ( ) ,
992+ mediaCard : box ( '#media-transform-card' ) ,
993+ mediaContent : box ( '#media-transform-usage' ) ,
994+ announcementForm : box ( '#announcement-form' ) ,
995+ } ;
996+ } ) ;
997+
998+ check ( platform . deniedHidden === true , 'Platform: yetkili hesaba "kapalı" ekranı gösteriliyor.' ) ;
999+ /* Ham enum sızıntısı. `info · all_agents · active` yerine Türkçe. */
1000+ check (
1001+ platform . announcementMeta === 'Kritik · Tüm ajanlar · Yayında' ,
1002+ `Platform: duyuru satırı ham enum yazıyor (${ platform . announcementMeta } ).` ,
1003+ ) ;
1004+ /* Yedek kartı: 34 satır yerine bir durum ve yalnız başarısız olan. */
1005+ check ( platform . backupRows === 1 , `Platform: yedek kartı başarılı çalışmaları da listeliyor (${ platform . backupRows } ).` ) ;
1006+ check ( platform . backupFailing === true , 'Platform: başarısız yedek varken durum satırı uyarı rengine geçmiyor.' ) ;
1007+ check (
1008+ platform . backupSummary ?. includes ( '1 yedek çalışması başarısız' ) ,
1009+ `Platform: yedek durumu başarısızlığı söylemiyor (${ platform . backupSummary } ).` ,
1010+ ) ;
1011+ check (
1012+ platform . backupSummary ?. includes ( '34 çalışmanın 33' ) ,
1013+ `Platform: yedek durumu toplamı yanlış (${ platform . backupSummary } ).` ,
1014+ ) ;
1015+ check (
1016+ platform . backupRowText ?. includes ( 'Haftalık · Başarısız' ) && platform . backupRowText ?. includes ( 'r2_upload_timeout' ) ,
1017+ `Platform: başarısız yedek satırı türü ya da hata kodunu yazmıyor (${ platform . backupRowText } ).` ,
1018+ ) ;
1019+ /* Boş kutu: kart bir dönem 990px yüksekliğindeydi ve içinde 80px
1020+ * içerik vardı, çünkü yanındaki uzun kartla eşit boya çekiliyordu. */
1021+ /* Boş kutu. Kart bir dönem 990px yüksekliğindeydi ve içinde 80px
1022+ * içerik vardı: `1fr 1fr` bir ızgarada komşusu uzun olduğu için
1023+ * onunla eşit boya çekiliyordu.
1024+ *
1025+ * Eşik ölçülerek seçildi: `align-items: start` varken kart ile
1026+ * içeriği arasındaki fark 82px (dolgu ve başlık), kaldırıldığında
1027+ * 189px'e çıkıyor. 120 ikisini ayırıyor. */
1028+ check (
1029+ platform . mediaCard . height - platform . mediaContent . height < 120 ,
1030+ `Platform: görsel bütçe kartı içeriğinden çok daha uzun (kart ${ platform . mediaCard . height } px, içerik ${ platform . mediaContent . height } px).` ,
1031+ ) ;
1032+ /* Duyuru formu bir dönem 310px'lik bir şeritti. */
1033+ check (
1034+ platform . announcementForm . width > 500 ,
1035+ `Platform: duyuru formu dar bir şeride sıkışmış (${ platform . announcementForm . width } px).` ,
1036+ ) ;
1037+ check (
1038+ await page . evaluate ( ( ) => document . documentElement . scrollWidth <= innerWidth ) ,
1039+ 'Platform: sayfa yatay taşıyor.' ,
1040+ ) ;
1041+ check ( pageErrors . length === 0 , `Platform: sayfa hatası: ${ pageErrors . join ( ' | ' ) } ` ) ;
1042+ await context . close ( ) ;
1043+ }
1044+
1045+ /* Yetkisi olmayan hesap. Sayfa bir kapı değil — uçlar sunucuda
1046+ * denetleniyor — ama çalışmayan düğmelerle dolu bir ekran da açmamalı. */
1047+ {
1048+ const context = await browser . newContext ( { viewport : { width : 1440 , height : 900 } , colorScheme : 'light' } ) ;
1049+ await context . addCookies ( [ { name : 'orbit-sponsor-test' , value : '1' , url : baseUrl } ] ) ;
1050+ const page = await context . newPage ( ) ;
1051+ const pageErrors = [ ] ;
1052+ page . on ( 'pageerror' , ( error ) => pageErrors . push ( error . message ) ) ;
1053+ await page . goto ( `${ baseUrl } /dashboard/platform` , { waitUntil : 'load' } ) ;
1054+ await page . waitForSelector ( '#platform-denied:not(.hidden)' ) ;
1055+ const denied = await page . evaluate ( ( ) => ( {
1056+ toolsHidden : document . querySelector ( '#platform' ) ?. classList . contains ( 'hidden' ) ,
1057+ approvals : document . querySelectorAll ( '#approvals .dashboard-item' ) . length ,
1058+ backups : document . querySelectorAll ( '#backups .dashboard-item, .backup-summary' ) . length ,
1059+ } ) ) ;
1060+ check ( denied . toolsHidden === true , 'Platform: yetkisiz hesaba araçlar gösteriliyor.' ) ;
1061+ check ( denied . approvals === 0 , 'Platform: yetkisiz hesap için inceleme kuyruğu yüklenmiş.' ) ;
1062+ check ( denied . backups === 0 , 'Platform: yetkisiz hesap için yedek listesi yüklenmiş.' ) ;
1063+ check ( pageErrors . length === 0 , `Platform yetkisiz: sayfa hatası: ${ pageErrors . join ( ' | ' ) } ` ) ;
1064+ await context . close ( ) ;
1065+ }
1066+
9021067 {
9031068 const context = await browser . newContext ( { viewport : { width : 390 , height : 844 } , colorScheme : 'light' } ) ;
9041069 const page = await context . newPage ( ) ;
0 commit comments