@@ -1064,6 +1064,9 @@ function extractIdsFromRawOutput(content: string): string[] {
10641064}
10651065
10661066async function withRecursiveLookupServer < T > (
1067+ options : {
1068+ replyContextPath ?: string ;
1069+ } ,
10671070 callback : ( server : {
10681071 rootUrl : URL ;
10691072 replyUrl : URL ;
@@ -1079,6 +1082,9 @@ async function withRecursiveLookupServer<T>(
10791082 const requestUrl = new URL ( request . url ) ;
10801083 const rootUrl = new URL ( "/notes/1" , requestUrl . origin ) ;
10811084 const replyUrl = new URL ( "/notes/0" , requestUrl . origin ) ;
1085+ const replyContextUrl = options . replyContextPath == null
1086+ ? undefined
1087+ : new URL ( options . replyContextPath , requestUrl . origin ) ;
10821088 requestedPaths . push ( requestUrl . pathname ) ;
10831089
10841090 let body : unknown ;
@@ -1092,10 +1098,25 @@ async function withRecursiveLookupServer<T>(
10921098 } ;
10931099 } else if ( requestUrl . pathname === replyUrl . pathname ) {
10941100 body = {
1095- "@context" : "https://www.w3.org/ns/activitystreams" ,
1101+ "@context" : replyContextUrl == null
1102+ ? "https://www.w3.org/ns/activitystreams"
1103+ : [
1104+ "https://www.w3.org/ns/activitystreams" ,
1105+ replyContextUrl . href ,
1106+ ] ,
10961107 id : replyUrl . href ,
10971108 type : "Note" ,
10981109 content : "reply" ,
1110+ ...( replyContextUrl == null ? { } : { fedifyTest : "value" } ) ,
1111+ } ;
1112+ } else if (
1113+ replyContextUrl != null &&
1114+ requestUrl . pathname === replyContextUrl . pathname
1115+ ) {
1116+ body = {
1117+ "@context" : {
1118+ fedifyTest : "https://fedify.dev/ns/test#fedifyTest" ,
1119+ } ,
10991120 } ;
11001121 } else {
11011122 return new Response ( null , { status : 404 } ) ;
@@ -1128,48 +1149,51 @@ test("runLookup - rejects recursive private targets by default", async () => {
11281149 const testFile = `${ testDir } /out.jsonl` ;
11291150 await mkdir ( testDir , { recursive : true } ) ;
11301151 try {
1131- await withRecursiveLookupServer ( async ( { rootUrl, requestedPaths } ) => {
1132- const originalWrite = process . stderr . write ;
1133- let stderr = "" ;
1134- process . stderr . write = ( (
1135- chunk : string | Uint8Array ,
1136- encodingOrCallback ?: unknown ,
1137- callback ?: ( ) => void ,
1138- ) => {
1139- stderr += typeof chunk === "string"
1140- ? chunk
1141- : Buffer . from ( chunk ) . toString ( ) ;
1142- if ( typeof encodingOrCallback === "function" ) {
1143- encodingOrCallback ( ) ;
1144- } else {
1145- callback ?.( ) ;
1152+ await withRecursiveLookupServer (
1153+ { } ,
1154+ async ( { rootUrl, requestedPaths } ) => {
1155+ const originalWrite = process . stderr . write ;
1156+ let stderr = "" ;
1157+ process . stderr . write = ( (
1158+ chunk : string | Uint8Array ,
1159+ encodingOrCallback ?: unknown ,
1160+ callback ?: ( ) => void ,
1161+ ) => {
1162+ stderr += typeof chunk === "string"
1163+ ? chunk
1164+ : Buffer . from ( chunk ) . toString ( ) ;
1165+ if ( typeof encodingOrCallback === "function" ) {
1166+ encodingOrCallback ( ) ;
1167+ } else {
1168+ callback ?.( ) ;
1169+ }
1170+ return true ;
1171+ } ) as typeof process . stderr . write ;
1172+ let exitCode : number | null ;
1173+ try {
1174+ exitCode = await runLookupAndCaptureExitCode (
1175+ createLookupRunCommand ( {
1176+ urls : [ rootUrl . href ] ,
1177+ recurse : "replyTarget" ,
1178+ recurseDepth : 20 ,
1179+ allowPrivateAddress : false ,
1180+ output : testFile ,
1181+ } ) ,
1182+ ) ;
1183+ } finally {
1184+ process . stderr . write = originalWrite ;
11461185 }
1147- return true ;
1148- } ) as typeof process . stderr . write ;
1149- let exitCode : number | null ;
1150- try {
1151- exitCode = await runLookupAndCaptureExitCode (
1152- createLookupRunCommand ( {
1153- urls : [ rootUrl . href ] ,
1154- recurse : "replyTarget" ,
1155- recurseDepth : 20 ,
1156- allowPrivateAddress : false ,
1157- output : testFile ,
1158- } ) ,
1186+ assert . equal ( exitCode , 1 ) ;
1187+ assert . deepEqual ( requestedPaths , [ "/notes/1" ] ) ;
1188+ assert . match (
1189+ stderr ,
1190+ / T r y w i t h ` - p ` \/ ` - - a l l o w - p r i v a t e - a d d r e s s ` / ,
11591191 ) ;
1160- } finally {
1161- process . stderr . write = originalWrite ;
1162- }
1163- assert . equal ( exitCode , 1 ) ;
1164- assert . deepEqual ( requestedPaths , [ "/notes/1" ] ) ;
1165- assert . match (
1166- stderr ,
1167- / T r y w i t h ` - p ` \/ ` - - a l l o w - p r i v a t e - a d d r e s s ` / ,
1168- ) ;
11691192
1170- const content = await readFile ( testFile , "utf8" ) ;
1171- assert . deepEqual ( extractIdsFromRawOutput ( content ) , [ rootUrl . href ] ) ;
1172- } ) ;
1193+ const content = await readFile ( testFile , "utf8" ) ;
1194+ assert . deepEqual ( extractIdsFromRawOutput ( content ) , [ rootUrl . href ] ) ;
1195+ } ,
1196+ ) ;
11731197 } finally {
11741198 await rm ( testDir , { recursive : true } ) ;
11751199 }
@@ -1181,6 +1205,7 @@ test("runLookup - allows recursive private targets with allowPrivateAddress", as
11811205 await mkdir ( testDir , { recursive : true } ) ;
11821206 try {
11831207 await withRecursiveLookupServer (
1208+ { } ,
11841209 async ( { rootUrl, replyUrl, requestedPaths } ) => {
11851210 const exitCode = await runLookupAndCaptureExitCode (
11861211 createLookupRunCommand ( {
@@ -1206,6 +1231,35 @@ test("runLookup - allows recursive private targets with allowPrivateAddress", as
12061231 }
12071232} ) ;
12081233
1234+ test ( "runLookup - keeps recursive private contexts blocked" , async ( ) => {
1235+ const testDir = "./test_output_runlookup_recurse_private_context" ;
1236+ const testFile = `${ testDir } /out.jsonl` ;
1237+ await mkdir ( testDir , { recursive : true } ) ;
1238+ try {
1239+ await withRecursiveLookupServer (
1240+ { replyContextPath : "/contexts/reply" } ,
1241+ async ( { rootUrl, requestedPaths } ) => {
1242+ const exitCode = await runLookupAndCaptureExitCode (
1243+ createLookupRunCommand ( {
1244+ urls : [ rootUrl . href ] ,
1245+ recurse : "replyTarget" ,
1246+ recurseDepth : 20 ,
1247+ allowPrivateAddress : true ,
1248+ output : testFile ,
1249+ } ) ,
1250+ ) ;
1251+ assert . equal ( exitCode , 1 ) ;
1252+ assert . deepEqual ( requestedPaths , [ "/notes/1" , "/notes/0" ] ) ;
1253+
1254+ const content = await readFile ( testFile , "utf8" ) ;
1255+ assert . deepEqual ( extractIdsFromRawOutput ( content ) , [ rootUrl . href ] ) ;
1256+ } ,
1257+ ) ;
1258+ } finally {
1259+ await rm ( testDir , { recursive : true } ) ;
1260+ }
1261+ } ) ;
1262+
12091263test ( "runLookup - reverses output order in default multi-input mode" , async ( ) => {
12101264 const testDir = "./test_output_runlookup_default_reverse" ;
12111265 const testFile = `${ testDir } /out.jsonl` ;
0 commit comments