
    EinR                        d 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
Z
ddlZddlmZ ddlmZmZmZ ddlZe	j&                  j)                  d e ee      j.                  j.                  j.                  dz  dz               ej0                  j3                  dd       ej0                  j3                  dd       ej0                  j3                  d	d       ej0                  j3                  d
d       ej0                  j3                  dd       ej0                  j3                  dd       ej0                  j3                  dd       ej0                  j3                  dd       ddlZ ej8                  d      d        Zej8                  d        Zej8                  d        Zej8                  d        Z ej8                  d        Z!ej8                  d        Z" G d d      Z# G d d      Z$ G d d      Z% G d d      Z& G d d       Z' G d! d"      Z( G d# d$      Z) G d% d&      Z* G d' d(      Z+ G d) d*      Z,y)+u  
Tests for Sunaiva Memory MCP — Canonical Unified Server
========================================================
Covers all 6 MCP tools with mocked external services (Qdrant, PostgreSQL, Redis).

Test count: 25
Categories:
  - Storage (5 tests)
  - Search (4 tests)
  - Retrieval (3 tests)
  - Deletion (3 tests)
  - Summary (2 tests)
  - Takeout Ingestion (3 tests)
  - User Isolation (3 tests)
  - Embedding (2 tests)

Run:
    cd /mnt/e/genesis-system
    python3 -m pytest tests/memory_mcp/test_sunaiva_memory.py -v
    N)Path)	MagicMockpatchPropertyMockmcp-serverssunaiva-memoryGENESIS_QDRANT_HOSTGENESIS_QDRANT_API_KEYGENESIS_POSTGRES_HOSTGENESIS_POSTGRES_PASSWORDGENESIS_REDIS_HOSTGENESIS_REDIS_PASSWORDGEMINI_API_KEYGEMINI_API_KEY_NEWT)autousec               #      K   dt         _        dt         _        dt         _        dt         _        d dt         _        dt         _        dt         _        dt         _        yw)z,Reset module-level singletons between tests.N)
mem_server_qdrant_client_pg_pool_redis_client_default_user_id     =/mnt/e/genesis-system/tests/memory_mcp/test_sunaiva_memory.pyreset_globalsr   8   sK      !%JJ#J"&J	 $JJ#J"&Js   AA c               #   f  K   t               } t               }t        j                  |_        t        |g      | j                  _        d| j                  _        g | j                  _        d| j                  _        t        j                  t        d|       5  |  ddd       y# 1 sw Y   yxY ww)z4Mock Qdrant client that simulates vector operations.)collectionsT_get_qdrantreturn_valueN)r   r   QDRANT_COLLECTIONnameget_collectionsr    upsertsearchdeleter   object)mock_clientmock_collections     r   mock_qdrantr*   F   s      +K  kO%77O/8oEV/WK, '+K# ')K# '+K#	j-k	J   s   BB1B%	B1%B.*B1c               #      K   i dfd	} t        j                  t        d|       5  t        j                  t        dt                     5   ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY ww)z0Mock PostgreSQL that simulates metadata storage.c                 2   | j                         j                         }|j                  d      rct        |      dk\  rT|d   |d   |d   }}}t        |      dkD  r|d   nd}|||t	        |t
              rt        j                  |      n|dd	|<   y |j                  d
      rd|v r|r|d   }d|v r|d   j                  d      j                         }t        |      dkD  r|d   nd}	g }
j                         D ]B  }|d   |k(  s||d   j                         v s"|
j                  |d   |d   |d   |d   f       D |
d |	 S t        |      dkD  r|d   nd}	g }
j                         D ],  }|d   |k(  s|
j                  |d   |d   |d   |d   f       . |
d |	 S |j                  d
      rd|v r|r|d   }|v r
|   d   fgS g S |j                  d
      r|rg S |j                  d      r|d   }|v r|= y |j                  d      ry |sd S g S )NINSERT   r            z{}z2026-02-26T12:00:00+00:00)iduser_idcontentmetadata
created_atSELECTzWHERE USER_IDILIKE%d   r3   r4   r2   r5   r6   zWHERE IDDELETECREATE)stripupper
startswithlen
isinstancestrjsonloadslowervaluesappend)queryparamsfetchq	memory_idr3   r4   metasearch_termlimitresultsmstored_memoriess               r   mock_executezmock_pg.<locals>.mock_executeb   sf   KKM!<<!6{a.4QiF1IG7	$'K!Ovay#&&4>tS4I

4 0t"=.	* \\(#1(<QiG!|$Qiooc288:%([1_q	#(//1 `A|w.;!I,BTBTBV3V$9q}aP\o'^_` v& &)[1_q	#(//1 `A|w.$9q}aP\o'^_` v&\\(#
aEq	IO+(3I>@AAI\\(#I\\(#q	IO+#I.\\(# t(b(r   _pg_executeside_effect_get_pgr   N)r   F)r   r'   r   r   )rS   rR   s    @r   mock_pgrX   ]   sl      O:)x 
j-\	J "\\*iikJ 	"!!	"" "	" 	"" "s4   %A;%A/A#A/	A;#A,	(A//A84A;c               #   `  K   i d	fd	} fd}fd}t        j                  t        d|       5  t        j                  t        d|      5  t        j                  t        d|      5   ddd       ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   yxY ww)
z"Mock Redis that simulates caching.c                     || <   y Nr   )keyvaluettlcaches      r   mock_cache_setz"mock_redis.<locals>.mock_cache_set   s    c
r   c                 &    j                  |       S r[   )getr\   r_   s    r   mock_cache_getz"mock_redis.<locals>.mock_cache_get   s    yy~r   c                 *    j                  | d        y r[   )poprc   s    r   mock_cache_deletez%mock_redis.<locals>.mock_cache_delete   s    		#tr   
_cache_setrU   
_cache_get_cache_deleteN)i  r   r'   r   )r`   rd   rg   r_   s      @r   
mock_redisrl      s      E 
j,N	K \\*lO 	j/GXY 	  	 	 sL   /B.B"B,B
1B9B"	B.
BBB	B""B+'B.c               #   ~   K   dd} t        j                  t        d|       5  |  ddd       y# 1 sw Y   yxY ww)zCMock embedding function that returns deterministic 768-dim vectors.Nc                     t        j                  | j                               j                         }|D cg c]  }t	        |      dz   }}|dt        |      z  dz   z  d d }|S c c}w )Ng     o@   r/   )hashlibsha256encodedigestfloatr@   )textapi_keyhbvecs        r   
fake_embedz"mock_embedding.<locals>.fake_embed   sc    NN4;;=)002)*+AuQx%++cSXo)*DS1
 ,s   A&get_embeddingrU   r[   rk   )rz   s    r   mock_embeddingr|      s7      
j/z	J   s   !=1	=:=c                     | |||dS )z/Combine all mocks for full integration testing.)qdrantpgredis	embeddingr   )r*   rX   rl   r|   s       r   	full_mockr      s     #	 r   c                   *    e Zd Zd Zd Zd Zd Zd Zy)TestMemoryStorec                    t        j                  t        j                  dd            }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   }|j                  }d} ||      }|stdt	        j                  |      t	        j                  |      t	        j                  |      t	        j                  |      dz  }	t        t	        j                  |	            dx}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)z8Store a simple memory and verify it returns a memory_id.zI prefer Python over JavaScript	test_user)r4   r3   rL   inz%(py1)s in %(py3)sresultpy1py3assert %(py5)spy5Nmem_Lassert %(py7)s
{%(py7)s = %(py3)s
{%(py3)s = %(py1)s.startswith
}(%(py5)s)
}r   r   r   py7storedTisz%(py1)s is %(py4)sr   py4assert %(py6)spy6)rC   rD   r   memory_store
@pytest_ar_call_reprcompare	_saferepr@py_builtinslocals_should_repr_global_nameAssertionError_format_explanationr?   )selfr   r   @py_assert0@py_assert2@py_format4@py_format6@py_assert4@py_assert6@py_format8@py_assert3@py_format5@py_format7s                r   test_store_basicz TestMemoryStore.test_store_basic   s8   J335
  ${f$$$${f$$${$$$$$$f$$$f$$$$$$$k"5"--5f5-f55555"555-555f5555555555h'4'4''''4''''''4'''''''r   c                    t        j                  ddd      }t        j                  t        j                  d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   }|j                  }d}	 ||	      }
|
stdt        j                  |      t        j                  |      t        j                  |	      t        j                  |
      dz  }t        t        j                  |            dx}x}x}	}
y)z$Store a memory with custom metadata.
preferenceprogramming)typecategoryzI use VSCode for editingr   )r4   r3   r5   r   Tr   r   r   r   r   NrL   r   r   r   )rC   dumpsrD   r   r   r   r   r   r   r   r?   )r   r   rM   r   r   r   r   r   r   r   r   r   s               r   test_store_with_metadataz(TestMemoryStore.test_store_with_metadata   s    zz<]KLJ33.
 
 h'4'4''''4''''''4'''''''k"5"--5f5-f55555"555-555f5555555555r   c                    t        j                  t        j                  dd            }t        j                  t        j                  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	)
zASame content + user_id should produce the same memory_id (dedup).ztest contentuser_arL   ==z%(py1)s == %(py4)sr   r   r   N	rC   rD   r   r   r   r   r   r   r   	r   r   r1r2r   r   r   r   r   s	            r   test_store_deterministic_idz+TestMemoryStore.test_store_deterministic_id   s    ZZ
//IJZZ
//IJ+1"[/1/1111/111111/1111111r   c                    t        j                  t        j                  dd            }t        j                  t        j                  dd            }|d   }|d   }||k7  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd	|iz  }t        t	        j                  |            d
x}x}}y
)zESame content for different users should produce different memory_ids.zshared contentr   user_brL   )!=)z%(py1)s != %(py4)sr   r   r   Nr   r   s	            r   (test_store_different_users_different_idsz8TestMemoryStore.test_store_different_users_different_ids   s    ZZ
//0@(KLZZ
//0@(KL+1"[/1/1111/111111/1111111r   c           
         t        j                  dd       |d   j                  j                          |d   j                  j                  }|j
                  }|j                  }d} ||      }t         j                  }||k(  }|s2t        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                  |      dt        j                         v st        j                  t               rt        j                  t               ndt        j                  |      d	z  }	d
d|	iz  }
t        t        j                  |
            dx}x}x}x}x}}y)z7Verify Qdrant upsert is called with correct collection.ztest memoryuser_xr~   collection_namer   )z%(py8)s
{%(py8)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.kwargs
}.get
}(%(py6)s)
} == %(py12)s
{%(py12)s = %(py10)s.QDRANT_COLLECTION
}call_kwargsr   )py0py2r   r   py8py10py12zassert %(py14)spy14N)r   r   r$   assert_called_once	call_argskwargsrb   r!   r   r   r   r   r   r   r   r   )r   r   r   @py_assert1r   @py_assert5@py_assert7@py_assert11@py_assert9@py_format13@py_format15s              r   test_store_qdrant_upsert_calledz/TestMemoryStore.test_store_qdrant_upsert_called   s   x8(""557)00::!!X!%%X&7X%&78XJ<X<XX8<XXXXX8<XXXXXXX{XXX{XXX!XXX%XXX&7XXX8XXXXXXJXXXJXXX<XXXXXXXXXr   N)__name__
__module____qualname__r   r   r   r   r   r   r   r   r   r      s    (	622Yr   r   c                   $    e Zd Zd Zd Zd Zd Zy)TestMemorySearchc                 V   t        j                  t        j                  dd            }|d   }g }||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            d	x}x}}y	)
z7Search with no matching memories returns empty results.znonexistent queryr   rP   r   r   r   r   r   N)	rC   rD   r   memory_searchr   r   r   r   r   r   r   r   r   r   r   r   r   s           r   test_search_empty_resultsz*TestMemorySearch.test_search_empty_results  sp    J445H(STi &B& B&&&& B&&& &&&B&&&&&&&r   c                 
   t        j                  dd       t        j                  t        j                  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}}d}|d   d   d   }|j                  } |       }	||	v }|st        j                  d|fd||	f      t        j                  |      t        j                  |      t        j                  |      t        j                  |	      dz  }
dd|
iz  }t        t        j                  |            dx}x}x}x}}	y)z?When Qdrant returns nothing, PostgreSQL keyword search is used.z+I love machine learning and neural networksr   zmachine learningcountr/   >=z%(py1)s >= %(py4)sr   r   r   NrP   r   r4   r   zD%(py1)s in %(py8)s
{%(py8)s = %(py6)s
{%(py6)s = %(py4)s.lower
}()
}r   r   r   r   assert %(py10)sr   )r   store_memoryrC   rD   r   r   r   r   r   r   rE   )r   r   r   r   r   r   r   r   r   r   @py_format9@py_format11s               r   test_search_pg_fallbackz(TestMemorySearch.test_search_pg_fallback  s    	 MxX J445GRSg#!#!####!######!#######!LVI%6q%9)%DL%D%J%JL%J%LL!%LLLLL!%LLLL!LLL%DLLL%JLLL%LLLLLLLLLr   c                    t               }d|_        d|_        ddddidd|_        |g|d	   j                  _        t        j                  t        j                  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}}|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}}|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}}|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)z7When Qdrant returns hits, they are formatted correctly.
mem_abc123g333333?r   zPython is my favourite languager   r   z2026-02-26T10:00:00)r3   r4   r5   r6   r~   zprogramming languager   r/   r   r   r   r   r   NrP   r   rL   scoresource)r   r2   r   payloadr%   r    rC   rD   r   r   r   r   r   r   r   )	r   r   mock_hitr   r   r   r   r   r   s	            r   test_search_qdrant_semanticz,TestMemorySearch.test_search_qdrant_semantic  s    ;"8./	
 4<*	(""/J445KXVWg#!#!####!######!#######i #K0@L@0L@@@@0L@@@0@@@L@@@@@@@i #G,44,4444,444,4444444444i #H-99-9999-999-9999999999r   c                    t        d      D ]  }t        j                  d| d        t        j                  t        j
                  d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)z$Search respects the limit parameter.   z Memory about topic alpha number r   ztopic alphar0   rO   r   <=z%(py1)s <= %(py4)sr   r   r   N)ranger   r   rC   rD   r   r   r   r   r   r   	r   r   ir   r   r   r   r   r   s	            r   test_search_respects_limitz+TestMemorySearch.test_search_respects_limit)  s     q 	VA##&Fqc$JHU	V J44]HTUVWg#!#!####!######!#######r   N)r   r   r   r   r   r   r   r   r   r   r   r     s    '
M:($r   r   c                       e Zd Zd Zd Zd Zy)TestMemoryGetAllc                 T   t        j                  t        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
   }g }||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            d	x}x}}y	)z-Get all memories for a user with no memories.nonexistent_userr   r   r   r   r   r   r   Nmemories)	rC   rD   r   memory_get_allr   r   r   r   r   r   s           r   test_get_all_emptyz#TestMemoryGetAll.test_get_all_empty9  s    J556HIJg#!#!####!######!#######j!'R'!R''''!R'''!'''R'''''''r   c                    t        j                  dd       t        j                  dd       t        j                  t        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}}y)z4Get all memories returns previously stored memories.zFirst memoryr   zSecond memoryr   r0   r   r   r   r   r   N)
r   r   rC   rD   r  r   r   r   r   r   r   s           r   test_get_all_returns_storedz,TestMemoryGetAll.test_get_all_returns_stored?  s    9:J55h?@g#!#!####!######!#######r   c                    t        d      D ]  }t        j                  d| d        t        j                  t        j
                  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)z%Get all respects the limit parameter.
   zMemory number r   r1   r   r   r   r   r   r   r   N)r   r   r   rC   rD   r  r   r   r   r   r   r   s	            r   test_get_all_respects_limitz,TestMemoryGetAll.test_get_all_respects_limitG  s    r 	DA##nQC$8(C	D J55haHIg#!#!####!######!#######r   N)r   r   r   r  r	  r  r   r   r   r  r  7  s    ($$r   r  c                       e Zd Zd Zd Zd Zy)TestMemoryDeletec                    t        j                  dd      }|d   }t        j                  t        j                  |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}}y)z Delete an existing memory by ID.zDelete me pleaser   rL   deletedTr   r   r   r   r   Nr   )z%(py1)s == %(py3)sr   r   r   )r   r   rC   rD   memory_deleter   r   r   r   r   r   r   r   )r   r   store_resultrL   r   r   r   r   r   r   r   r   s               r   test_delete_existingz%TestMemoryDelete.test_delete_existingV  s    !../A8L -	J44YIJi (D( D(((( D((( (((D(((((((k"/"i////"i///"//////i///i///////r   c                 T   t        j                  t        j                  d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
)z4Deleting a nonexistent memory returns deleted=False.mem_nonexistentr   r  Fr   r   r   r   r   N)	rC   rD   r   r  r   r   r   r   r   r   s           r   test_delete_nonexistentz(TestMemoryDelete.test_delete_nonexistent_  sp    J445FQRi )E) E)))) E))) )))E)))))))r   c                    t        j                  dd      }|d   }t        j                  t        j                  |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)z+A user cannot delete another user's memory.zPrivate memoryr   rL   r   r  Fr   r   r   r   r   N)
r   r   rC   rD   r  r   r   r   r   r   )
r   r   r  rL   r   r   r   r   r   r   s
             r   test_delete_wrong_userz'TestMemoryDelete.test_delete_wrong_userd  s    !../?J -	J44YIJi )E) E)))) E))) )))E)))))))r   N)r   r   r   r  r  r  r   r   r   r  r  T  s    0*
*r   r  c                       e Zd Zd Zd Zy)TestMemorySummarizec                 x   t        j                  d      }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	)
z7Summarize with no memories returns appropriate message.
empty_userzNo memories foundr   r   r   r   r   r   N)
r   memory_summarizer   r   r   r   r   r   r   r   r   r   r   r   r   r   r   s          r   test_summarize_emptyz(TestMemorySummarize.test_summarize_emptys  sn    ,,\:","f,,,,"f,,,",,,,,,f,,,f,,,,,,,r   c                 X   t        j                  ddddi       t        j                  ddddi       t        j                  ddddi       t        j                  d      }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)z"Summarize groups memories by type.zI like Pythonr   r   r   zMet John todayeventzDecided to use FastAPIdecisionzMemory Summaryr   r   r   r   r   r   NzTotal memories:)r   r   r  r   r   r   r   r   r   r   r   r  s          r   test_summarize_with_dataz,TestMemorySummarize.test_summarize_with_datax  s   FL;QR 0(VW<MN 8(VZDXY,,X6)6))))6)))))))))6)))6))))))) * F**** F*** ******F***F*******r   N)r   r   r   r  r#  r   r   r   r  r  q  s    -
+r   r  c                       e Zd Zd Zd Zd Zy)TestMemoryIngestTakeoutc                    ddddddg}|dz  }|j                  t        j                  |             t        j                  t	        j
                  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}||k(  }|slt        j                  d
|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}y)z-Ingest a simple JSON file with conversations.zFirst conversation about AIzAI Chat)r4   titlez Second conversation about PythonzPython Chatztakeout.jsonr   ingestedr0   r   r   r   r   r   Nerrorsr   
write_textrC   r   rD   r   memory_ingest_takeoutrB   r   r   r   r   r   r   r   tmp_pathdata	json_filer   r   r   r   r   r   s              r   test_ingest_json_filez-TestMemoryIngestTakeout.test_ingest_json_file  s    6	J:]S
 ~-	TZZ-.J<<S^XVWj!&Q&!Q&&&&!Q&&&!&&&Q&&&&&&&h$1$1$$$$1$$$$$$1$$$$$$$r   c                    t        j                  t        j                  dd            }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}|d   }|j                  } |       }	||	v }|st	        j
                  d|fd||	f      t	        j                  |      t	        j                  |      t	        j                  |      t	        j                  |	      dz  }
dd|
iz  }t        t	        j                  |            d
x}x}x}x}}	y
)z.Ingesting a nonexistent file returns an error.z/nonexistent/file.jsonr   errorr   r   r   r   r   r   Nz	not foundr   r   r   r   )rC   rD   r   r,  r   r   r   r   r   r   r   r   rE   )r   r   r   r   r   r   r   r   r   r   r   r   s               r   test_ingest_missing_filez0TestMemoryIngestTakeout.test_ingest_missing_file  s    J<<=UW_`a w&    w&   w      &   &       5fWo5o335355{55555{5555{555o5553555555555555r   c                    dddiddigig}|dz  }|j                  t        j                  |             t        j                  t	        j
                  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}}y)z#Ingest a Bard/Gemini format export.	responsesresponsez(Here is my answer about machine learningz%And here is another response about AIzbard_export.jsonr   r(  r/   r   r   r   r   r   Nr*  r-  s              r   test_ingest_bard_formatz/TestMemoryIngestTakeout.test_ingest_bard_format  s     !KL!HI
 11	TZZ-.J<<S^XVWj!&Q&!Q&&&&!Q&&&!&&&Q&&&&&&&r   N)r   r   r   r1  r4  r8  r   r   r   r%  r%    s    %6'r   r%  c                       e Zd Zd Zd Zd Zy)TestUserIsolationc                    t        j                  dd       t        j                  dd       t        j                  t        j                  d            }t        j                  t        j                  d            }|d   D cg c]  }|d   	 }}|d   D cg c]  }|d   	 }}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}}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c c}w c c}w )z@Memories stored by user A should not appear in user B's get_all.zUser A secretr   zUser B memoryr   r  r4   r   r   
a_contentsr   r   r   Nnot inz%(py1)s not in %(py3)s
b_contents)r   r   rC   rD   r  r   r   r   r   r   r   r   r   )r   r   result_aresult_brQ   r<  r@  r   r   r   r   s              r   test_user_a_cannot_see_user_bz/TestUserIsolation.test_user_a_cannot_see_user_b  s   ::::j77AB::j77AB,4Z,@Aqa	lA
A,4Z,@Aqa	lA
A,*,,,,*,,,,,,,,,*,,,*,,,,,,,0j0000j000000000j000j0000000,*,,,,*,,,,,,,,,*,,,*,,,,,,,0j0000j000000000j000j0000000 BAs   L<Mc                    t        j                  dd       t        j                  dd       t        j                  t        j                  dd            }|j                  dg       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}}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}} y)z<Search only returns memories belonging to the querying user.z!Confidential data about project Xr   z"Public information about project Xr   z	project XrP   Confidentialr4   r   )z%(py1)s in %(py4)sr   r   r   NPublicr=  )z%(py1)s not in %(py4)s)r   r   rC   rD   r   rb   r   r   r   r   r   )	r   r   r   rr   r   r   r   r   s	            r   test_search_isolated_by_userz.TestUserIsolation.test_search_isolated_by_user  s     CXN DhOJ44[(KLIr* 	0A!1Qy\1>\1111>\111>111\1111111/1Y</8<////8<///8///<///////	0r   c                    t        j                  dd      }|d   }t        j                  t        j                  |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}}t        j                  t        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}}y)z.A user cannot delete memories they do not own.zMy private noter   rL   r   r  Fr   r   r   r   r   Nr   r/   r   r   )r   r   rC   rD   r  r   r   r   r   r   r  )r   r   r  rL   delete_resultr   r   r   r   r   all_as              r   test_delete_isolated_by_userz.TestUserIsolation.test_delete_isolated_by_user  s    !../@(K -	 

:#;#;Ix#PQY'050'50000'5000'00050000000 

:44X>?W~""~""""~"""~""""""""""r   N)r   r   r   rC  rH  rL  r   r   r   r:  r:    s    1 0#r   r:  c                       e Zd Zd Zd Zy)TestEmbeddingc                 \   t        j                  d      }t        |      }t         j                  }||k(  }|s?t	        j
                  d|fd||f      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                  t               rt	        j                  t               ndt	        j                  |      dz  }dd	|iz  }t        t	        j                  |            d
x}x}}d |D        }t        |      }|sddt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      t	        j                  |      dz  }	t        t	        j                  |	            d
x}}y
)z:get_embedding returns a vector of exactly EMBED_DIM (768).z	test textr   )zO%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py7)s
{%(py7)s = %(py5)s.EMBED_DIM
}r@   ry   r   )r   r   r   r   r   zassert %(py9)spy9Nc              3   &   K   | ]	  }|d k(    yw)g        Nr   ).0vs     r   	<genexpr>z9TestEmbedding.test_embedding_dimension.<locals>.<genexpr>  s     )18)s   z,assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}all)r   r   r   )r   r{   r@   	EMBED_DIMr   r   r   r   r   r   r   r   rU  )
r   ry   r   r   r   r   @py_format10r   r   r   s
             r   test_embedding_dimensionz&TestEmbedding.test_embedding_dimension  s'    &&{33x/:///x/////x///////s///s//////3///3///x//////:///:///////////)S))s)))))))))s)))s))))))))))))))r   c                    t        j                  d      }t        j                  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
}t        |      }d}||k(  }|st        j                  d|fd||f      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                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            d
x}x}}y
)z:Without API key, embedding returns consistent zero vector.helloworldr   )z%(py0)s == %(py2)svec1vec2)r   r   zassert %(py4)sr   Nro   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)sr@   r   r   r   r   assert %(py8)sr   )r   r{   r   r   r   r   r   r   r   r   r@   )r   r\  r]  r   @py_format3r   r   r   r   r   r   s              r   !test_embedding_deterministic_zeroz/TestEmbedding.test_embedding_deterministic_zero  s   ''0''0t|tttttt4yCyCyCss44yCr   N)r   r   r   rX  rb  r   r   r   rN  rN    s    * r   rN  c                   $    e Zd Zd Zd Zd Zd Zy)
TestConfigc                    t        t              j                  j                  j                  dz  dz  dz  }|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}}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}}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<Verify no secrets are hardcoded in the server module source.r   r   z	server.py 7b74e6621bd0e6650789f6662bca4cbfr=  r?  r   r   r   r   NetY0eog17tDAIzaSye2ZyYYr4zos.environ.getr   r   )r   __file__parent	read_textr   r   r   r   r   r   r   r   )r   server_pathr   r   r   r   r   s          r   test_no_hardcoded_secretsz$TestConfig.test_no_hardcoded_secrets  s   8n++2299MIL\\_jj&&( 2?1????1???1????????????????*}F****}F***}******F***F*******%xv%%%%xv%%%x%%%%%%v%%%v%%%%%%%'z''''z'''z'''''''''''''''')6))))6)))))))))6)))6)))))))r   c                    t         j                  }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  t               rt        j                  t               ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)	zVerify EMBED_DIM is set to 768.ro   r   )z1%(py2)s
{%(py2)s = %(py0)s.EMBED_DIM
} == %(py5)sr   r   r   r   assert %(py7)sr   N)
r   rV  r   r   r   r   r   r   r   r   r   r   r   r   r   r   s         r   test_embed_dim_constantz"TestConfig.test_embed_dim_constant  st    ##*s*#s****#s******z***z***#***s*******r   c                    t         j                  }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  t               rt        j                  t               ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)	z.Verify Qdrant collection name is standardised.sunaiva_memory_768r   )z9%(py2)s
{%(py2)s = %(py0)s.QDRANT_COLLECTION
} == %(py5)sr   rp  rq  r   N)
r   r!   r   r   r   r   r   r   r   r   rr  s         r   test_collection_namezTestConfig.test_collection_name  sx    ++C/CC+/CCCCC+/CCCCCCCzCCCzCCC+CCC/CCCCCCCCr   c                    t        j                  dd      }|j                  }d} ||      }|sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }t        t        j                  |            dx}x}}t        |      }d}||k(  }|st        j                  d	|fd
||f      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                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)z-Memory IDs follow the mem_ prefix convention.testuserr   zLassert %(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.startswith
}(%(py4)s)
}mid)r   r   r   r   N   r   r^  r@   r_  r`  r   )r   _generate_memory_idr?   r   r   r   r   r   r   r   r@   r   )	r   rz  r   r   r   r   r   r   r   s	            r   test_memory_id_formatz TestConfig.test_memory_id_format  s   ,,VV<~~%f%~f%%%%%%%%s%%%s%%%~%%%f%%%%%%%%%%3x2x2~x2ss33x2r   N)r   r   r   rn  rs  rv  r}  r   r   r   rd  rd    s    *+Dr   rd  c                       e Zd Zd Zd Zd Zy)TestDefaultUserIdc                 .   dt         _        t         j                  }d} ||      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t               rt        j                  t               ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}x}x}}y	)
z'Explicit user_id overrides the default.default_userexplicit_userr   zV%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s._resolve_user_id
}(%(py4)s)
} == %(py9)sr   r   r   r   r   rP  assert %(py11)spy11Nr   r   _resolve_user_idr   r   r   r   r   r   r   r   r   r   r   r   @py_assert8r   rW  @py_format12s           r   test_resolve_explicit_userz,TestDefaultUserId.test_resolve_explicit_user  s    &4
#**N?N*?;NN;NNNN;NNNNNNzNNNzNNN*NNN?NNN;NNNNNNNNNNNr   c                 .   dt         _        t         j                  }d} ||      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t               rt        j                  t               ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}y)	z6Default user_id is used when no explicit one provided.r  Nr   r  r   r  r  r  r  r  s           r   test_resolve_default_userz+TestDefaultUserId.test_resolve_default_user  s    &4
#**B4B*40BNB0NBBBB0NBBBBBBzBBBzBBB*BBB4BBB0BBBNBBBBBBBBr   c                     dt         _        t        j                  t        d      5  t        j
                  d       ddd       y# 1 sw Y   yxY w)z2Missing user_id with no default raises ValueError.Nzuser_id is required)match)r   r   pytestraises
ValueErrorr  )r   s    r   test_resolve_no_user_raisesz-TestDefaultUserId.test_resolve_no_user_raises"  s<    &*
#]]:-BC 	.''-	. 	. 	.s   AAN)r   r   r   r  r  r  r   r   r   r  r    s    O
C
.r   r  )-__doc__builtinsr   _pytest.assertion.rewrite	assertionrewriter   rC   ossysrp   tempfilepathlibr   unittest.mockr   r   r   r  pathinsertrB   rj  rk  environrf   serverr   fixturer   r*   rX   rl   r|   r   r   r   r  r  r  r%  r:  rN  rd  r  r   r   r   <module>r     s  *   	 
    8 8  3tH~,,33::]JM]]^ _ 

$d + 

' . 

& - 

*D 1 

#T * 

' . 

 & 

#T *  
' 
'  , B" B"J  &    (Y (Y^,$ ,$f$ $:* *:+ +,#' #'T'# '#\   * D. .r   