
    _id                       d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlZddlmZ ddlmZmZmZmZ ddlmZmZmZ ddlmZmZ ddlZej6                  j9                  d e ee      j>                  j>                  j>                                e       Z  e       Z!e dd	d
df	 	 	 	 	 	 	 	 	 	 	 ddZ" G d d      Z# G d d      Z$ G d d      Z% G d d      Z& G d d      Z'e(dk(  r ejR                  eddg       yy)ut  RLM Neo-Cortex -- Integration Layer Tests.

Tests for:
  - config.py   (TestConfig)
  - middleware.py (TestMiddleware)
  - app.py       (TestApp)
  - mcp_bridge.py (TestMCPBridge)

All external dependencies (MemoryGateway, asyncpg, redis, JWT) are mocked
so the test suite runs in CI without live Elestio connections.

Test count target: >= 30 tests across 4 classes.

VERIFICATION_STAMP
Story: integration-layer-app, integration-layer-middleware,
       integration-layer-config, integration-layer-mcp-bridge
Verified By: parallel-builder
Verified At: 2026-02-26T12:00:00Z
Tests: 35 tests — see results below
Coverage: >=90%
    )annotationsN)Path)AnyDictListOptional)	AsyncMock	MagicMockpatch)UUIDuuid4zTest memory contentepisodicg?c                *   ddl m }m} ddlm} t	               }| |_        ||_        d|_        d|_         ||      |_	        ||_
        |xs t        t                     |_        d|_        i |_        |j!                  |j"                        |_        |S )z&Build a mock MemoryRecord-like object.r   )datetimetimezone)
MemoryTiermcpgeneral   )r   r   core.rlm.contractsr   r
   	tenant_idcontentsourcedomainmemory_tiersurprise_scorestrr   	vector_idpg_idmetadatanowutc
created_at)	r   r   tierscorer   r   r   r   recs	            +/mnt/e/genesis-system/tests/rlm/test_app.py_mock_recordr(   .   sz     ,-
+CCMCKCJCJ &COC-UWCMCICL\\(,,/CNJ    c                  p    e Zd ZdZddZ	 	 	 	 ddZddZ	 	 	 	 ddZddZ	 	 	 	 ddZ		 	 	 	 ddZ
dd	Zy
)
TestConfigz,Black-box and white-box tests for config.py.c                *   |j                  dd       ddlm} ddlmc m}  ||       |j                  }|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      d	z  }d
d|iz  }	t        t        j                  |	            dx}x}x}}y)z&BB: DATABASE_URL env var is picked up.DATABASE_URLz#postgresql://user:pass@host:5432/dbr   reloadN==)zR%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.settings
}.database_url
} == %(py7)scfg_modpy0py2py4py7assert %(py9)spy9)setenv	importlibr/   core.rlm.configrlmconfigsettingsdatabase_url
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanation
selfmonkeypatchr/   r2   @py_assert1@py_assert3@py_assert6@py_assert5@py_format8@py_format10s
             r'    test_settings_reads_database_urlz+TestConfig.test_settings_reads_database_urlO   s    >+PQ$))wU,,U0UU,0UUUUU,0UUUUUUUwUUUwUUUUUU,UUU0UUUUUUUUr)   c                   |j                  dd       |j                  dd       |j                  dd       |j                  dd	       |j                  d
d       |j                  dd       ddlm} ddlmc m}  ||       d}|j                  }|j                  }||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                   |	            dx}x}x}}d}|j                  }|j                  }||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                   |	            dx}x}x}}y)z<BB: Falls back to building DSN from GENESIS_POSTGRES_* vars.r-   FraisingGENESIS_POSTGRES_HOSTzpg.elestio.appGENESIS_POSTGRES_PORT25432GENESIS_POSTGRES_USERmyuserGENESIS_POSTGRES_PASSWORDs3cr3tGENESIS_POSTGRES_DATABASEmydbr   r.   Nin)zR%(py1)s in %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s.settings
}.database_url
}r2   py1py3py5r7   r8   r9   )delenvr:   r;   r/   r<   r=   r>   r?   r@   rA   rB   rF   rC   rD   rE   rG   rH   
rJ   rK   r/   r2   @py_assert0@py_assert4rN   @py_assert2rP   rQ   s
             r'   0test_settings_builds_pg_dsn_from_individual_varsz;TestConfig.test_settings_builds_pg_dsn_from_individual_varsX   s    	>5924DE2G<2H=6A6?$))w@7#3#3@#3#@#@@#@@@@@#@@@@@@@@@@7@@@7@@@#3@@@#@@@@@@@@87++8+888x88888x8888x88888878887888+88888888888r)   c                   |j                  dd       ddlm} ddlmc m}  ||       d}|j                  }|j                  }||v }|st        j                  d|fd||f      t        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }dd|iz  }	t        t        j                  |	            dx}x}x}}d}|j                  }|j                  }||v }|st        j                  d|fd||f      t        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }dd|iz  }	t        t        j                  |	            dx}x}x}}y)z$BB: CORS_ORIGINS is split on commas.CORS_ORIGINSzhttps://a.com,https://b.comr   r.   Nzhttps://a.comr_   )zR%(py1)s in %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s.settings
}.cors_origins
}r2   ra   r8   r9   zhttps://b.com)r:   r;   r/   r<   r=   r>   r?   cors_originsrA   rB   rF   rC   rD   rE   rG   rH   rf   s
             r'   !test_settings_cors_origins_parsedz,TestConfig.test_settings_cors_origins_parsedi   s7   >+HI$))w?'"2"2?"2"?"??"?????"??????????'???'???"2???"?????????'"2"2?"2"?"??"?????"??????????'???'???"2???"????????r)   c                   |j                  dd       |j                  dd       |j                  dd       |j                  dd       |j                  dd       |j                  dd       d	d
lm} d	dlmc m}  ||       t        j                  t        d      5  |j                  j                          ddd       y# 1 sw Y   yxY w)zUBB: validate() raises ValueError when DATABASE_URL, QDRANT_URL, REDIS_URL are absent.r-   FrT   
QDRANT_URL	REDIS_URLrV   GENESIS_QDRANT_HOSTGENESIS_REDIS_HOSTr   r.   NzMissing environment variablesmatch)re   r;   r/   r<   r=   r>   pytestraises
ValueErrorr?   validaterJ   rK   r/   r2   s       r'   (test_settings_validate_raises_on_missingz3TestConfig.test_settings_validate_raises_on_missings   s     	>59<7;62EB0%@/?$))w]]:-LM 	(%%'	( 	( 	(s   %C		Cc                    |j                  dd       |j                  dd       |j                  dd       ddlm} dd	lmc m}  ||       |j                  j                          y	)
z<BB: validate() is silent when all required env vars are set.r-   zpostgresql://u:p@h/drp   zhttps://qdrant.example.com:6333rq   zredis://default:pwd@host:6379/0r   r.   N)r:   r;   r/   r<   r=   r>   r?   ry   rz   s       r'   test_settings_validate_passesz(TestConfig.test_settings_validate_passes   sW    >+AB<)JK;(IJ$))w!!#r)   c                *   |j                  dd       ddlm} ddlmc m}  ||       |j                  }|j                  }d}||k(  }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      t        j                  |      d
z  }dd|iz  }	t        t        j                  |	            dx}x}x}}y)zEWB: _env_int gracefully falls back to default for non-integer values.RLM_PORTnot_a_numberr   r.   N  r0   zN%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.settings
}.rlm_port
} == %(py7)sr2   r3   r8   r9   )r:   r;   r/   r<   r=   r>   r?   rlm_portrA   rB   rC   rD   rE   rF   rG   rH   rI   s
             r'   'test_env_int_returns_default_on_invalidz2TestConfig.test_env_int_returns_default_on_invalid   s     	:~6$))w0((0D0(D0000(D000000w000w000000(000D0000000r)   c                   ddl m} dD ]  }|j                  d|       d} ||      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      d	z  }d
d|iz  }	t        t        j                  |	            dx}x}x}} y)z2WB: _env_bool returns True for 'yes', '1', 'true'.r   )	_env_bool)yes1trueTrueTRUETEST_BOOL_FLAGTisz0%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} is %(py7)sr   r3   r8   r9   N)r<   r   r:   rA   rB   rC   rD   rE   rF   rG   rH   )
rJ   rK   r   truthyrL   rM   rN   rO   rP   rQ   s
             r'   $test_env_bool_accepts_truthy_stringsz/TestConfig.test_env_bool_accepts_truthy_strings   s     	.: 	7F/8-69-.6$6.$6666.$66666696669666-666.666$6666666	7r)   c                ,   |j                  dd       ddlm} ddlmc m}  ||       |j                  }|j                  }d}||k(  }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}x}}y)z4WB: rlm_port defaults to 8100 when RLM_PORT not set.r   FrT   r   r.   Nr   r0   r   r2   r3   r8   r9   )re   r;   r/   r<   r=   r>   r?   r   rA   rB   rC   rD   rE   rF   rG   rH   rI   s
             r'   test_rlm_port_defaultz TestConfig.test_rlm_port_default   s    :u5$))w0((0D0(D0000(D000000w000w000000(000D0000000r)   NrK   zpytest.MonkeyPatchreturnNone)__name__
__module____qualname____doc__rR   rj   rn   r{   r}   r   r   r    r)   r'   r+   r+   K   so    6V9-9	9"@(-(	("$1-1	17-7	71r)   r+   c                      e Zd ZdZddZddZddZej                  j                  	 	 	 	 dd       Z
ej                  j                  	 	 	 	 dd       ZddZddZdd	Zy
)TestMiddlewarez0Black-box and white-box tests for middleware.py.c                   ddl }d}t        }|j                  dt        |      i|d      }ddlm}  |||d      }||k(  }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }	t        t        j                  |	            d}y)z2BB: Valid JWT with tenant_id claim returns a UUID.r   Ntest_secret_123r   HS256	algorithm_decode_jwtr0   z%(py0)s == %(py2)sresulttidr4   r5   assert %(py4)sr6   jwtTENANT_Aencoder   core.rlm.middlewarer   rA   rB   rC   rD   rE   rF   rG   rH   
rJ   r   secretr   tokenr   r   rL   @py_format3@py_format5s
             r'   ,test_decode_jwt_returns_uuid_for_valid_tokenz;TestMiddleware.test_decode_jwt_returns_uuid_for_valid_token   s    "

KS2Fg
N3UFG4}vvvr)   c                n   ddl m}  |ddd      }d}||u }|st        j                  d|fd||f      d	t	        j
                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
z  }dd|iz  }t        t        j                  |            dx}}y)z0BB: Invalid JWT returns None instead of raising.r   r   z	not.a.jwtr   r   Nr   z%(py0)s is %(py3)sr   r4   rc   assert %(py5)srd   )
r   r   rA   rB   rC   rD   rE   rF   rG   rH   )rJ   r   r   ri   rL   @py_format4@py_format6s          r'   *test_decode_jwt_returns_none_for_bad_tokenz9TestMiddleware.test_decode_jwt_returns_none_for_bad_token   sk    3[(G<v~vvvr)   c                   ddl }d}t        }|j                  dt        |      i|d      }ddlm}  |||d      }||k(  }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }	t        t        j                  |	            d}y)z1BB: 'sub' claim is used when tenant_id is absent.r   Nr   subr   r   r   r0   r   r   r   r   r   r6   r   r   s
             r'   *test_decode_jwt_uses_sub_claim_as_fallbackz9TestMiddleware.test_decode_jwt_uses_sub_claim_as_fallback   s    

E3s8,f
H3UFG4}vvvr)   c                ^  K   ddl }d}t        }|j                  dt        |      i|d      }|j	                  d|       ddlm}  |d	d
| i       d{   }||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  |      rt        j                  |      nddz  }	dd|	iz  }
t        t        j                  |
            d}y7 ԭw)z=BB: extract_tenant_from_headers returns UUID from JWT header.r   Nheader_secretr   r   r   
JWT_SECRETextract_tenant_from_headersAuthorizationzBearer r0   r   r   r   r   r   r6   )r   r   r   r   r:   r   r   rA   rB   rC   rD   rE   rF   rG   rH   )rJ   rK   r   r   r   r   r   r   rL   r   r   s              r'   $test_extract_tenant_from_headers_jwtz3TestMiddleware.test_extract_tenant_from_headers_jwt   s     
 	 

KS2Fg
N<0C2OwugEV3WXX}vvv Ys   AD-D+CD-c                  K   |j                  dd       ddlm}  |i        d{   }d}||u }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      d	z  }d
d|iz  }t        t        j                  |            dx}}y7 w)z2BB: Returns None when no auth headers are present.r   some_secretr   r   Nr   r   r   r   r   rd   )r:   r   r   rA   rB   rC   rD   rE   rF   rG   rH   )rJ   rK   r   r   ri   rL   r   r   s           r'   =test_extract_tenant_from_headers_returns_none_without_headerszLTestMiddleware.test_extract_tenant_from_headers_returns_none_without_headers   s     
 	<7C2266v~vvv 7s   $CCB+Cc                Z   ddl m} d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d
x}}y
)z'WB: /health is in the exempt paths set.r   )_EXEMPT_PATHS/healthr_   z%(py1)s in %(py3)sr   rb   rc   r   rd   N)
r   r   rA   rB   rF   rC   rD   rE   rG   rH   )rJ   r   rg   ri   r   r   s         r'   !test_exempt_paths_contains_healthz0TestMiddleware.test_exempt_paths_contains_health   s^    5)yM))))yM)))y))))))M)))M)))))))r)   c                ,   ddl m}m} |sedddt        j                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            d}||u}|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
z  }dd|iz  }t        t        j                  |            dx}}y)z?WB: TenantMiddleware class is available from middleware module.r   )TenantMiddleware_MIDDLEWARE_AVAILABLEzassert %(py0)sr4   r   Nis not)z%(py0)s is not %(py3)sr   r   r   rd   )r   r   r   rC   rD   rA   rE   rF   rG   rH   rB   )rJ   r   r   @py_format1ri   rL   r   r   s           r'   !test_tenant_middleware_importablez0TestMiddleware.test_tenant_middleware_importable   s    O$$$$$$$$$$$$$$$$'++t++++t++++++++++++t+++++++r)   c                   ddl }t        }|j                  dt        |      idd      }ddlm}  ||dd      }d}||u }|st        j                  d	|fd
||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}}y)z2WB: JWT signed with different secret returns None.r   Nr   real_secretr   r   r   wrong_secretr   r   r   r   r   rd   r   )
rJ   r   r   r   r   r   ri   rL   r   r   s
             r'   ,test_decode_jwt_returns_none_on_wrong_secretz;TestMiddleware.test_decode_jwt_returns_none_on_wrong_secret   s    

KS2MW
U3UNG<v~vvvr)   Nr   r   r   )r   r   r   r   r   r   r   rv   markasyncior   r   r   r   r   r   r)   r'   r   r      s    : [[-	  [[-	 *,r)   r   c                  X    e Zd ZdZddZddZddZddZddZddZ	ddZ
dd	Zdd
Zy)TestAppz)Black-box and white-box tests for app.py.c                   ddl m} t               }d|_        t        d      |_        t        d      |_        t        ddddd      |_        t        d|      5  t        d	d      5  dd
lm	} ddl
mc m}  ||       ||_         ||j                  d      }||fcddd       cddd       S # 1 sw Y   nxY wddd       y# 1 sw Y   yxY w)z1Build a TestClient with all backend calls mocked.r   )
TestClientTNreturn_valuehealthystatuspgqdrantrediszcore.rlm.app.MemoryGatewayz!core.rlm.config.settings.validater.   )raise_server_exceptions)fastapi.testclientr   r	   is_initialized
initializeclosehealth_checkr   r;   r/   core.rlm.appr=   app_gateway)rJ   r   mock_gwr/   app_modclients         r'   _get_test_clientzTestApp._get_test_client
  s    1 +!%&D9!t4($-TTTXY 
 /gF 	#6TJ	#(**7O&GTJF7?	# 	# 	# 	# 	# 	# 	#s$   C	+6B4!	C	4B=	9C		Cc                   | j                         \  }}|j                  d      }|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}}|j                         }	d
}
|
|	v }|st        j                  d|fd|
|	f      t        j                  |
      dt        j                         v st        j                  |	      rt        j                  |	      nddz  }dd|iz  }t        t        j                  |            d	x}
}d}
|
|	v }|st        j                  d|fd|
|	f      t        j                  |
      dt        j                         v st        j                  |	      rt        j                  |	      nddz  }dd|iz  }t        t        j                  |            d	x}
}y	)z1BB: /health returns HTTP 200 with gateway status.r      r0   z3%(py2)s
{%(py2)s = %(py0)s.status_code
} == %(py5)sresponser4   r5   rd   assert %(py7)sr7   Nr   r_   r   datar   r   rd   gatewayr   getstatus_coderA   rB   rC   rD   rE   rF   rG   rH   json)rJ   r   r   r   rL   rh   rM   r   rP   r   rg   ri   r   s                r'    test_health_endpoint_returns_200z(TestApp.test_health_endpoint_returns_200!  sQ   //1::i(##*s*#s****#s******x***x***#***s*******}}x4x4x44 yD    yD   y      D   D       r)   c           
     V   | j                         \  }}|j                  d      }|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}}|j                  } |       }|j                  }	d
}
 |	|
      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |	      t        j                  |
      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}x}	x}
x}x}}y	)z)BB: / returns HTTP 200 with service name./r   r0   r   r   r   r   r7   Nservicezrlm-neo-cortex)z}%(py10)s
{%(py10)s = %(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.json
}()
}.get
}(%(py8)s)
} == %(py13)sr4   r5   r6   py6py8py10py13assert %(py15)spy15r   )rJ   r   _r   rL   rh   rM   r   rP   rO   @py_assert7@py_assert9@py_assert12@py_assert11@py_format14@py_format16s                   r'   test_root_endpoint_returns_200z&TestApp.test_root_endpoint_returns_200+  sY   ))+	::c?##*s*#s****#s******x***x***#***s*******}}A}A""A9A"9-A1AA-1AAAAA-1AAAAAAAxAAAxAAA}AAAAAA"AAA9AAA-AAA1AAAAAAAAAr)   c                   | j                         \  }}t        ddddd      |j                  _        |j	                  d      }|j
                  }d}||v }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            dx}x}}y)z0BB: /api/v1/memory/health endpoint is reachable.r   Tr   r   z/api/v1/memory/health)r   i  r_   )z3%(py2)s
{%(py2)s = %(py0)s.status_code
} in %(py5)sr   r   r   r7   N)r   r	   r   r   r   r   rA   rB   rC   rD   rE   rF   rG   rH   )	rJ   r   r   r   rL   rh   rM   r   rP   s	            r'   test_memory_router_mountedz"TestApp.test_memory_router_mounted3  s    //1(1$-TTTXY)
% ::56##1z1#z1111#z111111x111x111#111z1111111r)   c                   | j                         \  }}|j                  di       }|j                  }d}||k7  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}y
)z3BB: /api/v1/feedback/webhook endpoint is reachable.z/api/v1/feedback/webhook)r  i  )!=)z3%(py2)s
{%(py2)s = %(py0)s.status_code
} != %(py5)sr   r   r   r7   N)r   postr   rA   rB   rC   rD   rE   rF   rG   rH   )	rJ   r   r  r   rL   rh   rM   r   rP   s	            r'   test_feedback_router_mountedz$TestApp.test_feedback_router_mounted?  s    ))+	;;9;C##*s*#s****#s******x***x***#***s*******r)   c           	        | j                         \  }}|j                  dddd      }g }|j                  }|j                  }d} ||      }d}	||	u}
|
}|
s|j                  }d}||v }|}|st        j                  d	|
fd
||	f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      t        j                  |	      dz  }dd|iz  }|j                  |       |
st        j                  dfdf      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }|j                  |       t        j                  |d      i z  }dd|iz  }t        t        j                  |            dx}x}x}x}x}x}x}
x}	x}x}}y)z0BB: CORS headers are injected by CORSMiddleware.r   zhttp://localhost:3000GET)OriginzAccess-Control-Request-Method)headerszaccess-control-allow-originN)r   i  r   )zm%(py10)s
{%(py10)s = %(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s.headers
}.get
}(%(py8)s)
} is not %(py13)sr   )r5   r6   r  r  r	  r
  z%(py15)sr  r_   )z7%(py19)s
{%(py19)s = %(py17)s.status_code
} in %(py22)s)py17py19py22z%(py24)spy24r   zassert %(py27)spy27)r   optionsr  r   r   rA   rB   rC   rD   rE   rF   append_format_booloprG   rH   )rJ   r   r  r   rL   rM   rO   r  r  r  r  rg   @py_assert18@py_assert21@py_assert20r  r  @py_format23@py_format25@py_format26@py_format28s                        r'   test_cors_headers_presentz!TestApp.test_cors_headers_presentG  s4   ))+	>>6Y^_ " 

	2x 	2## 	2$A 	2#$AB 	2$ 	2B$N 	2##	2'1	2#z1	2 	2 1 1	2B$ 	2 	2+16	2 	211  	2 	2(1	  	2 	2(1	   	2 	2(1	 $ 	2 	2(1	 %B 	2 	2(1	 C 	2 	2(1	 KO 	2 	2 	2+16	2+1	2 1 1	2#z	2 	2+16	2 	211x	2 	2(1	x	2 	2(1	#	2 	2(1	z	2 	2 	2+16	2+1	2#1>	2 	2 	2 	211	2 	2 	2 	2 	2r)   c                    ddl mc m} |j                  }	 d|_        t	        j
                  t        d      5  |j                          ddd       ||_        y# 1 sw Y   xY w# ||_        w xY w)z<WB: get_gateway() raises RuntimeError when _gateway is None.r   Nznot initialisedrt   )r   r=   r   r   rv   rw   RuntimeErrorget_gateway)rJ   r   originals      r'   #test_get_gateway_raises_before_initz+TestApp.test_get_gateway_raises_before_initS  se    &&##	(#G|3DE &##%&  (G& &  (Gs"   "A& A
A& A#A& &	A/c                v   ddl m} ddlm} ddlmc m} |j                         }t        ||      }|sddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      d	z  }t        t        j                  |            d}y)
zQWB: create_app() returns a FastAPI application (no external dependencies needed).r   FastAPIr.   Nz5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstancer   r6  )r4   rb   r5   r6   )fastapir6  r;   r/   r   r=   r   
create_appr7  rC   rD   rA   rE   rF   rG   rH   )rJ   r6  r/   r   r   rM   r   s          r'   test_create_app_returns_fastapiz'TestApp.test_create_app_returns_fastapi_  s    # 	%&&##%&'********z***z******&***&******'***'**********r)   c                   ddl m} ddlmc m} |j                  }t        ||      }|sddt        j                         v st        j                  t
              rt        j                  t
              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        t        j                  |            dx}}y)	z+WB: core.rlm.app.app is a FastAPI instance.r   r5  NzNassert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.app
}, %(py4)s)
}r7  r   r6  )r4   rb   rc   r6   r  )r8  r6  r   r=   r   r7  rC   rD   rA   rE   rF   rG   rH   )rJ   r6  r   ri   rO   @py_format7s         r'   test_app_module_exports_appz#TestApp.test_app_module_exports_appj  s    #&&!++/z+w////////z///z//////'///'///+//////w///w//////////r)   N)r   r   r   )r   r   r   r   r   r  r  r  r  r.  r3  r:  r=  r   r)   r'   r   r     s3    3#.!B	2+	2	(+0r)   r   c                  B   e Zd ZdZddZej                  j                  dd       Zej                  j                  dd       Z	ej                  j                  dd       Z
ej                  j                  dd       Zej                  j                  dd       Zej                  j                  dd       Zej                  j                  dd	       Zdd
ZddZddZej                  j                  dd       Zej                  j                  dd       ZddZy)TestMCPBridgez0Black-box and white-box tests for mcp_bridge.py.c                    ddl m} t               }d|_        t        d      |_        t        d      |_         ||      }d|_        ||fS )z0Return an MCPBridge with a mocked MemoryGateway.r   	MCPBridgeTNr   r   )core.rlm.mcp_bridgerB  r	   r   r   r   _initializedrJ   rB  r   bridges       r'   _mock_bridgezTestMCPBridge._mock_bridgex  sI    1+!%&D9!t47+"wr)   c                (  K   | j                         \  }}t        t        d      }t        |      |_        |j                  dt        t                     d{   }|j                  j                          t        j                  |      }|d   }d}||k(  }|slt        j                  d|fd	||f      t        j                  |      t        j                  |      d
z  }	dd|	iz  }
t        t        j                  |
            dx}x}}|d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      d
z  }	dd|	iz  }
t        t        j                  |
            dx}x}}y7 4w)z6BB: memory_store translates to gateway.write_memory().r   )r   r$   r   z'Customer prefers email over phone callsr   user_idNr$   r0   z%(py1)s == %(py4)srb   r6   assert %(py6)sr  storedTr   z%(py1)s is %(py4)s)rH  r(   r   r	   write_memorymemory_storer   assert_awaited_oncer  loadsrA   rB   rF   rG   rH   )rJ   rG  r   expected_record
result_strr   rg   rM   ri   r   r<  s              r'   $test_memory_store_calls_write_memoryz2TestMCPBridge.test_memory_store_calls_write_memory  s     ++-&
K(oF!..=M / 
 

 	002J'f~++~++++~+++~++++++++++h'4'4''''4''''''4'''''''
s   AFFD4Fc                  K   | j                         \  }}|j                  dd       d{   }t        j                  |      }|d   }d}||u }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      d	z  }d
d|iz  }	t        t	        j                  |	            dx}x}}d}||v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }
dd|
iz  }t        t	        j                  |            dx}}|j                  j                          y7 Yw)z>BB: memory_store returns a warning when user_id is not a UUID.zSome content herez
not-a-uuidrJ  NrO  Fr   rP  rM  rN  r  warningr_   r   r   r   r   rd   )rH  rR  r  rT  rA   rB   rF   rG   rH   rC   rD   rE   rQ  assert_not_awaitedrJ   rG  r   rV  r   rg   rM   ri   r   r<  r   r   s               r'   #test_memory_store_warns_on_non_uuidz1TestMCPBridge.test_memory_store_warns_on_non_uuid  s     ++-!..'  / 
 

 J'h(5(5((((5((((((5((((((("yF""""yF"""y""""""F"""F"""""""//1
s   *FFEFc                8  K   | j                         \  }}t        t        d      }t        |g      |_        |j                  dt        t                     d{   }|j                  j                          t        j                  |      }|d   }d}||k(  }|slt        j                  d	|fd
||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}|d   d   d   }d}||k(  }|slt        j                  d	|fd
||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}y7 ;w)z:BB: memory_search translates to gateway.search_memories().zImportant fact)r   r   r   	importantqueryrK  Ncountr   r0   rL  rM  rN  r  resultsr   r   )rH  r(   r   r	   search_memoriesmemory_searchr   rS  r  rT  rA   rB   rF   rG   rH   )rJ   rG  r   r&   rV  r   rg   rM   ri   r   r<  s              r'   (test_memory_search_calls_search_memoriesz6TestMCPBridge.test_memory_search_calls_search_memories  s/     ++-X7GH"+#"?!//M 0 
 

 	335J'g#!#!####!######!#######i #I.B2BB.2BBBBB.2BBBB.BBB2BBBBBBBB
s   AFFD;Fc                  K   | j                         \  }}|j                  dd       d{   }t        j                  |      }|d   }d}||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      d	z  }d
d|iz  }	t        t	        j                  |	            dx}x}}d}||v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }
dd|
iz  }t        t	        j                  |            dx}}y7 @w)z8BB: Non-UUID user_id returns empty results with warning.r`  zlegacy-userr_  Nra  r   r0   rL  rM  rN  r  rY  r_   r   r   r   r   rd   )rH  rd  r  rT  rA   rB   rF   rG   rH   rC   rD   rE   r[  s               r'   -test_memory_search_returns_empty_for_non_uuidz;TestMCPBridge.test_memory_search_returns_empty_for_non_uuid  s      ++-!//g}/UU
J'g#!#!####!######!#######"yF""""yF"""y""""""F"""F""""""" Vs   *E/E,E E/c                ~  K   | j                         \  }}t        d      |_        t        t	                     }|j                  |t        t                     d{   }|j                  j                          t        j                  |      }|d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	d	d
|	iz  }
t        t        j                  |
            dx}x}}|d   }||k(  }|st        j                  d|fd||f      t        j                  |      dt        j                          v st        j"                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}y7 ]w)z8BB: memory_delete translates to gateway.delete_memory().Tr   	memory_idrK  Ndeletedr   rP  rM  rN  r  rj  r0   z%(py1)s == %(py3)smidr   r   rd   )rH  r	   delete_memoryr   r   memory_deleter   rS  r  rT  rA   rB   rF   rG   rH   rC   rD   rE   )rJ   rG  r   rm  rV  r   rg   rM   ri   r   r<  r   r   s                r'    test_memory_delete_calls_gatewayz.TestMCPBridge.test_memory_delete_calls_gateway  s0     ++- )t <%'l!//M 0 
 

 	113J'i (D( D(((( D((( (((D(((((((k")"c))))"c)))"))))))c)))c)))))))
s   AF=F:EF=c                  K   | j                         \  }}t        t        dd      }t        |      |_        |j                  dt        t                     d{   }t        j                  |      }|d   }d	}||u }|slt        j                  d
|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}y7 w)z-BB: DISCARD-tier record returns stored=False.discardg?)r   r$   r%   r   z-Low-value content that scores below thresholdrJ  NrO  Fr   rP  rM  rN  r  r$   r0   rL  )rH  r(   r   r	   rQ  rR  r   r  rT  rA   rB   rF   rG   rH   )rJ   rG  r   discard_recrV  r   rg   rM   ri   r   r<  s              r'   3test_memory_store_discard_tier_returns_stored_falsezATestMCPBridge.test_memory_store_discard_tier_returns_stored_false  s     ++-"XITR(kB!..CM / 
 

 J'h(5(5((((5((((((5(((((((f~**~****~***~**********
s   AE9E6DE9c                  K   | j                         \  }}t        ddddd      |_        |j                          d{   }|d   }d}||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}|d   }d}||u }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}y7 w)z;BB: health() returns status dict with pg/qdrant/redis keys.r   Tr   r   Nr   r0   rL  rM  rN  r  r   r   rP  )	rH  r	   r   healthrA   rB   rF   rG   rH   )	rJ   rG  r   r   rg   rM   ri   r   r<  s	            r'   test_health_returns_dictz&TestMCPBridge.test_health_returns_dict  s      ++-($-TTTXY 
 }}&h,9,9,,,,9,,,,,,9,,,,,,,d|#t#|t####|t###|###t####### 's   =EE DEc                   ddl m}m}  |         |       } |       }||u }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d
} |        y
)z=BB: get_bridge() returns the same instance on repeated calls.r   )
get_bridgereset_bridger   )z%(py0)s is %(py2)sb1b2r   r   r6   N)rD  ry  rz  rA   rB   rC   rD   rE   rF   rG   rH   )rJ   ry  rz  r{  r|  rL   r   r   s           r'   !test_get_bridge_returns_singletonz/TestMCPBridge.test_get_bridge_returns_singleton  sh    @\\RxrRrrRRr)   c                2   ddl m} d} ||      }d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }d	d
|iz  }t        t        j                  |            dx}x}x}}d} ||      }d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }d	d
|iz  }t        t        j                  |            dx}x}x}}d} ||      }d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }d	d
|iz  }t        t        j                  |            dx}x}x}}y)z9WB: _resolve_tenant returns None for empty/blank strings.r   _resolve_tenant Nr   r   r  r3   r8   r9   z   )
rD  r  rA   rB   rC   rD   rE   rF   rG   rH   )rJ   r  rL   rM   rN   rO   rP   rQ   s           r'   *test_resolve_tenant_returns_none_for_emptyz8TestMCPBridge.test_resolve_tenant_returns_none_for_empty  s   7!*r"*d*"d****"d************r***"***d*******$-u%--%----%------------u---%----------#,t$,,$,,,,$,,,,,,,,,,,,t,,,$,,,,,,,,,,r)   c                   ddl m} t        j                  } ||      }|t        k(  }|st	        j
                  d|fd|t        f      dt        j                         v st	        j                  |      rt	        j                  |      nddt        j                         v st	        j                  t              rt	        j                  t              nddz  }dd	|iz  }t        t	        j                  |            d
}y
)z;WB: _resolve_tenant handles hex UUID string without dashes.r   r  r0   r   r   r   r   r   r6   N)rD  r  r   hexrA   rB   rC   rD   rE   rF   rG   rH   )rJ   r  hex_uuidr   rL   r   r   s          r'   #test_resolve_tenant_parses_hex_uuidz1TestMCPBridge.test_resolve_tenant_parses_hex_uuid  s    7<< *!!!!v!!!!!!v!!!v!!!!!!!!!!!!!!!!r)   c                   K   ddl m} t               }d|_         ||      }|j	                          d{    |j	                          d{    |j                  j                          y7 77 !w)zHWB: Calling initialize() twice does not call gateway.initialize() twice.r   rA  TrC  N)rD  rB  r	   r   r   rS  rF  s       r'   test_initialize_is_idempotentz+TestMCPBridge.test_initialize_is_idempotent  sc      	2+!%7+!!!!!!..0 	"!s!   4A2A.A2A0 A20A2c           
       K   | j                         \  }}t        g       |_        |j                  dt	        t
              d       d{    |j                  j                  }|j                  }|j                  }d}t        |j                        dkD  r|j                  d   nd} |||      }d}	||	k  }
|
st        j                  d	|
fd
||	f      dt        j                         v st        j                  |      rt        j                   |      ndt        j                   |      t        j                   |      t        j                   |      t        j                   |      t        j                   |      t        j                   |	      dz  }dd|iz  }t#        t        j$                  |            dx}x}x}x}x}x}
}	y7 |w)z8WB: Limit is clamped to 100 even when caller passes 999.r   testi  )r`  rK  limitNr     d   )<=)zq%(py10)s
{%(py10)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.kwargs
}.get
}(%(py6)s, %(py8)s)
} <= %(py13)scall_kwargsr  r  r  )rH  r	   rc  rd  r   r   	call_argskwargsr   lenargsrA   rB   rC   rD   rE   rF   rG   rH   )rJ   rG  r   r  rL   rM   rO   r  r  r  r  r  r  s                r'   test_memory_search_clamps_limitz-TestMCPBridge.test_memory_search_clamps_limit  sJ     ++-"+"<""Xc"RRR--77!!p!%%pgpc+JZJZF[^_F_{/?/?/Behp%g/hipmppimpppppimppppppp{ppp{ppp!ppp%pppgppp/hpppipppmppppppppp 	Ss   A	G
GE<G
c                2   ddl m}m} t               } |t	                     } |||       |j
                  }|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      d	z  }	d
d|	iz  }
t        t        j                  |
            dx}x}x}}y)zNWB: register_rlm_tools() registers memory_store, memory_search, memory_delete.r   )register_rlm_toolsrB  rC  )rG     r0   )zL%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.tool
}.call_count
} == %(py7)smock_mcpr3   r8   r9   N)rD  r  rB  r
   r	   tool
call_countrA   rB   rC   rD   rE   rF   rG   rH   )rJ   r  rB  r  mock_bridgerL   rM   rN   rO   rP   rQ   s              r'   'test_register_rlm_tools_attaches_to_mcpz5TestMCPBridge.test_register_rlm_tools_attaches_to_mcp  s    E;	48K8}},}'',1,'1,,,,'1,,,,,,x,,,x,,,},,,',,,1,,,,,,,r)   N)r   z'MCPBridge'r   )r   r   r   r   rH  rv   r   r   rW  r\  re  rg  rp  rt  rw  r}  r  r  r  r  r  r   r)   r'   r?  r?  u  s0   :	 [[( (  [[
2 
2 [[C C  [[# # [[* *  [[+ + [[$ $-" [[
1 
1 [[q q	-r)   r?  c                  J    e Zd ZdZej
                  j                  dd       Zy)TestIntegrationu=   End-to-end smoke test: MCPBridge store → search → delete.c                  K   ddl m} t               }d|_        t	        t                     }t        t        ddd|      }t        t        ddd|      }t        |      |_        t        |g      |_	        t        d      |_
         ||	      }d|_        t        j                  |j                  dt	        t              
       d{         }|d   }d}	||	u }
|
slt        j                   d|
fd||	f      t        j"                  |      t        j"                  |	      dz  }dd|iz  }t%        t        j&                  |            dx}x}
}	|d   }||k(  }
|
st        j                   d|
fd||f      t        j"                  |      dt)        j*                         v st        j,                  |      rt        j"                  |      nddz  }dd|iz  }t%        t        j&                  |            dx}}
t        j                  |j/                  dt	        t                     d{         }|d   }d}	||	k(  }
|
slt        j                   d|
fd||	f      t        j"                  |      t        j"                  |	      dz  }dd|iz  }t%        t        j&                  |            dx}x}
}	d}|d   d   d   }	||	v }
|
slt        j                   d|
fd ||	f      t        j"                  |      t        j"                  |	      dz  }dd|iz  }t%        t        j&                  |            dx}x}
}	t        j                  |j1                  |t	        t              !       d{         }|d"   }d}	||	u }
|
slt        j                   d|
fd||	f      t        j"                  |      t        j"                  |	      dz  }dd|iz  }t%        t        j&                  |            dx}x}
}	y7 ,7 7 w)#z<BB+WB: Full round-trip: store, search, delete via MCPBridge.r   rA  Tz(Customer uses Mac mini, prefers Terminalr   gQ?)r   r   r$   r%   r   r   rC  rJ  NrO  r   rP  rM  rN  r  rj  r0   rl  r   r   rd   zMac minir_  ra  r   rL  rb  r   r_   )z%(py1)s in %(py4)sri  rk  )rD  rB  r	   r   r   r   r(   r   rQ  rc  rn  rE  r  rT  rR  rA   rB   rF   rG   rH   rC   rD   rE   rd  ro  )rJ   rB  r   rj  
stored_rec	found_recrG  store_resultrg   rM   ri   r   r<  r   r   search_resultdelete_results                    r'   "test_store_search_delete_roundtripz2TestIntegration.test_store_search_delete_roundtrip1  s     	2+!%L	!>

 !>
	  )jA"+)"E )t <7+" zz%%BH &  
 H%--%----%---%----------K(5(I5555(I555(555555I555I5555555 

&&ZX&OO
 W%**%****%***%**********C]95a8CCzCCCCCzCCCCzCCCCCCCCCCC 

&&CM&RR
 Y'/4/'4////'4///'///4///////' P Ss9   C	O?O7E"O?.O:/D?O?.O=/B	O?:O?=O?Nr   )r   r   r   r   rv   r   r   r  r   r)   r'   r  r  .  s!    G[[30 30r)   r  __main__z-vz
--tb=short)r   r   r   r   r$   r   r%   floatr   zOptional[str]r   r
   )*r   
__future__r   builtinsrC   _pytest.assertion.rewrite	assertionrewriterA   r  ossyspathlibr   typingr   r   r   r   unittest.mockr	   r
   r   uuidr   r   rv   pathinsertr   __file__parentr   TENANT_Br(   r+   r   r   r?  r  r   mainr   r)   r'   <module>r     s!  * #    	 
  , , 5 5   3tH~,,33::; < 77 (#  	
  :_1 _1LO Olg0 g0\r- r-r70 70| zFKK4./ r)   