@@ -4,6 +4,47 @@ import { test } from "node:test";
44
55const source = readFileSync ( new URL ( "../index.ts" , import . meta. url ) , "utf8" ) ;
66
7+ function extractFunctionBody ( name ) {
8+ const signatureIndex = source . indexOf ( `async function ${ name } ` ) ;
9+ assert . notEqual ( signatureIndex , - 1 , `${ name } signature not found` ) ;
10+ const bodyStart = source . indexOf ( "{\n let res" , signatureIndex ) ;
11+ let depth = 0 ;
12+ for ( let index = bodyStart ; index < source . length ; index += 1 ) {
13+ const char = source [ index ] ;
14+ if ( char === "{" ) depth += 1 ;
15+ if ( char === "}" ) depth -= 1 ;
16+ if ( depth === 0 ) return source . slice ( bodyStart + 1 , index ) ;
17+ }
18+ throw new Error ( `${ name } body not found` ) ;
19+ }
20+
21+ function buildEngramFetchForTest ( ) {
22+ const body = extractFunctionBody ( "engramFetch" )
23+ . replace ( "let res: Response | undefined;" , "let res;" )
24+ . replace ( "let data: unknown = null;" , "let data = null;" )
25+ . replace ( "return data as TResponse;" , "return data;" ) ;
26+ const factory = new Function ( "fetch" , "wait" , "redactUrlPath" , "redactValue" , "ENGRAM_URL" , `
27+ class EngramHttpError extends Error {
28+ constructor(message, status, data) {
29+ super(message);
30+ this.name = "EngramHttpError";
31+ this.status = status;
32+ this.data = data;
33+ }
34+ }
35+ return async function engramFetch(path, opts = {}) {
36+ ${ body }
37+ };
38+ ` ) ;
39+ return factory (
40+ globalThis . fetch ,
41+ ( ) => Promise . resolve ( ) ,
42+ ( value ) => value ,
43+ ( value ) => value ,
44+ "http://127.0.0.1:7437" ,
45+ ) ;
46+ }
47+
748test ( "mem_session_summary accepts explicit project fallback" , ( ) => {
849 assert . match ( source , / m e m _ s e s s i o n _ s u m m a r y : T y p e \. O b j e c t \( \{ [ \s \S ] * p r o j e c t : o p t i o n a l S t r i n g \( " O p t i o n a l p r o j e c t t o u s e w h e n a u t o m a t i c d e t e c t i o n i s u n a v a i l a b l e " \) / ) ;
950 assert . match ( source , / c a s e " m e m _ s e s s i o n _ s u m m a r y " : [ \s \S ] * i f \( ! r e q u e s t e d P r o j e c t \) r e q u i r e R e s o l v e d P r o j e c t \( \) ; [ \s \S ] * e n s u r e S e s s i o n \( a c t i v e S e s s i o n I d , a c t i v e P r o j e c t \) [ \s \S ] * p r o j e c t : a c t i v e P r o j e c t / ) ;
@@ -28,6 +69,60 @@ test("ambiguous_project error maps to actionable status label, not generic 'erro
2869 assert . doesNotMatch ( source , / s e t S t a t u s \? \. \( " e n g r a m " , \s * ` 🧠 \$ \{ p r o j e c t \} · e r r o r ` \) / ) ;
2970} ) ;
3071
72+ test ( "memory protocol declares gentle-engram as the Pi-native provider" , ( ) => {
73+ assert . match ( source , / T h e s e i n s t r u c t i o n s a r e i n j e c t e d b y g e n t l e - e n g r a m , t h e P i - n a t i v e m e m o r y p r o v i d e r / ) ;
74+ assert . match ( source , / U s e t h e m e m o r y t o o l s n a m e d i n t h i s s e c t i o n a s t h e a u t h o r i t a t i v e P i m e m o r y c o n t r a c t / ) ;
75+ assert . match ( source , / D o n o t i n f e r a l t e r n a t i v e E n g r a m t o o l n a m e s f r o m o t h e r i n t e g r a t i o n s / ) ;
76+ } ) ;
77+
78+ test ( "native tool fetches retry transient HTTP startup failures" , async ( ) => {
79+ const originalFetch = globalThis . fetch ;
80+ let calls = 0 ;
81+ globalThis . fetch = async ( ) => {
82+ calls += 1 ;
83+ if ( calls < 3 ) throw new Error ( "connection refused" ) ;
84+ return {
85+ ok : true ,
86+ async json ( ) {
87+ return { status : "ok" } ;
88+ } ,
89+ } ;
90+ } ;
91+ try {
92+ const engramFetch = buildEngramFetchForTest ( ) ;
93+ assert . deepEqual ( await engramFetch ( "/health" ) , { status : "ok" } ) ;
94+ assert . equal ( calls , 3 ) ;
95+ } finally {
96+ globalThis . fetch = originalFetch ;
97+ }
98+ } ) ;
99+
100+ test ( "native tool fetch preserves HTTP error status" , async ( ) => {
101+ const originalFetch = globalThis . fetch ;
102+ globalThis . fetch = async ( ) => ( {
103+ ok : false ,
104+ status : 503 ,
105+ async json ( ) {
106+ return { error : "server warming up" } ;
107+ } ,
108+ } ) ;
109+ try {
110+ const engramFetch = buildEngramFetchForTest ( ) ;
111+ await assert . rejects (
112+ ( ) => engramFetch ( "/search" ) ,
113+ ( error ) => error . name === "EngramHttpError" && error . status === 503 && error . message === "server warming up" ,
114+ ) ;
115+ } finally {
116+ globalThis . fetch = originalFetch ;
117+ }
118+ } ) ;
119+
120+ test ( "native tool unavailable error names the Pi-native HTTP path" , ( ) => {
121+ assert . match ( source , / g e n t l e - e n g r a m c o u l d n o t r e a c h t h e E n g r a m H T T P s e r v e r / ) ;
122+ assert . match ( source , / P i - n a t i v e m e m _ \* t o o l s a r e r e g i s t e r e d / ) ;
123+ assert . match ( source , / R u n m e m _ d o c t o r o r r e s t a r t E n g r a m / ) ;
124+ } ) ;
125+
31126test ( "mem_review is registered as a Pi-native executable memory tool" , ( ) => {
32127 assert . match ( source , / c o n s t E N G R A M _ T O O L S = \[ [ \s \S ] * " m e m _ r e v i e w " / ) ;
33128 assert . match ( source , / m e m _ r e v i e w : T y p e \. O b j e c t \( \{ [ \s \S ] * a c t i o n : T y p e \. S t r i n g \( \{ d e s c r i p t i o n : " A c t i o n : l i s t \| m a r k _ r e v i e w e d " \} \) / ) ;
0 commit comments