Skip to content

Repository files, collaboration, releases and project activity.

ARCHFORGE HISTORY

Blame · tests/redirect-monitor.test.js

Branch main · the Commit author is not necessarily a verified ArchForge account.

6b0b538dd6archadmin2026-10-111 'use strict';
6b0b538dd6archadmin2026-10-112 const test=require('node:test');
6b0b538dd6archadmin2026-10-113 const assert=require('node:assert/strict');
6b0b538dd6archadmin2026-10-114 const http=require('node:http');
6b0b538dd6archadmin2026-10-115 const {checkHttp,runProbe}=require('../src/probes');
6b0b538dd6archadmin2026-10-116 
6b0b538dd6archadmin2026-10-117 async function withServer(fn){
6b0b538dd6archadmin2026-10-118  const s=http.createServer((req,res)=>{
6b0b538dd6archadmin2026-10-119  const route=req.url.split('?')[0];
6b0b538dd6archadmin2026-10-1110  const redirect=(to,code=302)=>{res.writeHead(code,{location:to});res.end();};
6b0b538dd6archadmin2026-10-1111  if(route==='/')return redirect('/login');
6b0b538dd6archadmin2026-10-1112  if(route==='/login'){res.writeHead(200,{'content-type':'text/html'});res.end('<html>Login</html>');return;}
6b0b538dd6archadmin2026-10-1113  if(route==='/healthy'){res.writeHead(200,{'content-type':'text/html'});res.end('<html>Site</html>');return;}
6b0b538dd6archadmin2026-10-1114  if(route==='/one')return redirect('/two',301);
6b0b538dd6archadmin2026-10-1115  if(route==='/two')return redirect('/healthy',308);
6b0b538dd6archadmin2026-10-1116  if(route==='/offsite')return redirect('https://untrusted.example/');
6b0b538dd6archadmin2026-10-1117  if(route==='/downgrade')return redirect('http://evil.invalid/');
6b0b538dd6archadmin2026-10-1118  if(route==='/credentials')return redirect('http://user:password@127.0.0.1/');
6b0b538dd6archadmin2026-10-1119  if(route==='/bad-scheme')return redirect('file:///etc/passwd');
6b0b538dd6archadmin2026-10-1120  if(route==='/self')return redirect('/self');
6b0b538dd6archadmin2026-10-1121  if(route==='/no-location'){res.writeHead(302);res.end();return;}
6b0b538dd6archadmin2026-10-1122  if(route==='/chain1')return redirect('/chain2');
6b0b538dd6archadmin2026-10-1123  if(route==='/chain2')return redirect('/chain3');
6b0b538dd6archadmin2026-10-1124  if(route==='/chain3')return redirect('/chain4');
6b0b538dd6archadmin2026-10-1125  if(route==='/chain4')return redirect('/healthy');
6b0b538dd6archadmin2026-10-1126  if(route==='/servererror'){res.writeHead(500);res.end('Internal Server Error');return;}
6b0b538dd6archadmin2026-10-1127  if(route==='/git'){res.writeHead(302,{location:'/login'});res.end();return;}
6b0b538dd6archadmin2026-10-1128  res.writeHead(404);res.end();
6b0b538dd6archadmin2026-10-1129  });
6b0b538dd6archadmin2026-10-1130  await new Promise(r=>s.listen(0,'127.0.0.1',r));
6b0b538dd6archadmin2026-10-1131  try{return await fn('http://127.0.0.1:'+s.address().port);}finally{await new Promise(r=>s.close(r));}
6b0b538dd6archadmin2026-10-1132 }
6b0b538dd6archadmin2026-10-1133 
6b0b538dd6archadmin2026-10-1134 test('real-world HTTP 302 to same-origin login: website reachable, clearly marked reachability-only',async()=>withServer(async base=>{
6b0b538dd6archadmin2026-10-1135  const r=await checkHttp(base+'/',1200,{website:true});
6b0b538dd6archadmin2026-10-1136  assert.equal(r.success,true);
6b0b538dd6archadmin2026-10-1137  assert.match(r.detail,/HTTP 200 via 302/);
6b0b538dd6archadmin2026-10-1138  assert.match(r.detail,/reachability only/);
6b0b538dd6archadmin2026-10-1139  assert.equal((await runProbe('website',{website:base+'/',timeout:1200})).success,true);
6b0b538dd6archadmin2026-10-1140 }));
2109dd33b8archadmin2026-10-1241 test('API and ArchForge HTTPS never follow 302 to a login page',async()=>withServer(async base=>{
6b0b538dd6archadmin2026-10-1142  const api=await checkHttp(base+'/',1200);
6b0b538dd6archadmin2026-10-1143  const git=await checkHttp(base+'/git',1200,{git:true});
6b0b538dd6archadmin2026-10-1144  assert.equal(api.success,false);assert.match(api.detail,/redirect rejected/);
6b0b538dd6archadmin2026-10-1145  assert.equal(git.success,false);assert.match(git.detail,/redirect rejected/);
6b0b538dd6archadmin2026-10-1146 }));
6b0b538dd6archadmin2026-10-1147 test('safe 301 + 308 hops and actual website 200 count as reachable',async()=>withServer(async base=>{
6b0b538dd6archadmin2026-10-1148  const r=await checkHttp(base+'/one',1200,{website:true});
6b0b538dd6archadmin2026-10-1149  assert.equal(r.success,true);assert.match(r.detail,/2 same-origin hops/);
6b0b538dd6archadmin2026-10-1150 }));
6b0b538dd6archadmin2026-10-1151 test('external origins, protocol change, login credential injection and file scheme all rejected',async()=>withServer(async base=>{
6b0b538dd6archadmin2026-10-1152  for(const route of ['/offsite','/downgrade','/credentials','/bad-scheme']){
6b0b538dd6archadmin2026-10-1153  const r=await checkHttp(base+route,1200,{website:true});
6b0b538dd6archadmin2026-10-1154  assert.equal(r.success,false,route);assert.match(r.detail,/unsafe redirect/,route);
6b0b538dd6archadmin2026-10-1155  }
6b0b538dd6archadmin2026-10-1156 }));
6b0b538dd6archadmin2026-10-1157 test('redirect loops, 4th redirects, missing Location rejected',async()=>withServer(async base=>{
6b0b538dd6archadmin2026-10-1158  for(const [route,error] of [['/self',/redirect loop/],['/chain1',/redirect limit/],['/no-location',/missing Location/]]){
6b0b538dd6archadmin2026-10-1159  const r=await checkHttp(base+route,1200,{website:true});
6b0b538dd6archadmin2026-10-1160  assert.equal(r.success,false,route);assert.match(r.detail,error,route);
6b0b538dd6archadmin2026-10-1161  }
6b0b538dd6archadmin2026-10-1162 }));
6b0b538dd6archadmin2026-10-1163 test('HTTP 500 or 404 never reported as healthy',async()=>withServer(async base=>{
6b0b538dd6archadmin2026-10-1164  const e=await checkHttp(base+'/servererror',1200,{website:true});
6b0b538dd6archadmin2026-10-1165  const missing=await checkHttp(base+'/missing',1200,{website:true});
6b0b538dd6archadmin2026-10-1166  assert.equal(e.success,false);assert.match(e.detail,/HTTP 500/);
6b0b538dd6archadmin2026-10-1167  assert.equal(missing.success,false);assert.match(missing.detail,/HTTP 404/);
6b0b538dd6archadmin2026-10-1168 }));