Skip to content
This repository was archived by the owner on Jun 5, 2025. It is now read-only.

Commit 8e788e5

Browse files
committed
Unit test the de-obfuscation of secrets
1 parent d0e46c8 commit 8e788e5

File tree

1 file changed

+147
-0
lines changed

1 file changed

+147
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import pytest
2+
from litellm import ModelResponse
3+
from litellm.types.utils import Delta, StreamingChoices
4+
5+
from codegate.pipeline.base import PipelineContext, PipelineSensitiveData
6+
from codegate.pipeline.output import OutputPipelineContext
7+
from codegate.pipeline.secrets.manager import SecretsManager
8+
from codegate.pipeline.secrets.secrets import SecretUnredactionStep
9+
10+
11+
def create_model_response(content: str) -> ModelResponse:
12+
"""Helper to create test ModelResponse objects"""
13+
return ModelResponse(
14+
id="test",
15+
choices=[
16+
StreamingChoices(
17+
finish_reason=None,
18+
index=0,
19+
delta=Delta(content=content, role="assistant"),
20+
logprobs=None,
21+
)
22+
],
23+
created=0,
24+
model="test-model",
25+
object="chat.completion.chunk",
26+
)
27+
28+
29+
class TestSecretUnredactionStep:
30+
def setup_method(self):
31+
"""Setup fresh instances for each test"""
32+
self.step = SecretUnredactionStep()
33+
self.context = OutputPipelineContext()
34+
self.secrets_manager = SecretsManager()
35+
self.session_id = "test_session"
36+
37+
# Setup input context with secrets manager
38+
self.input_context = PipelineContext()
39+
self.input_context.sensitive = PipelineSensitiveData(
40+
manager=self.secrets_manager, session_id=self.session_id
41+
)
42+
43+
@pytest.mark.asyncio
44+
async def test_complete_marker_processing(self):
45+
"""Test processing of a complete REDACTED marker"""
46+
# Store a secret
47+
encrypted = self.secrets_manager.store_secret(
48+
"secret_value", "test_service", "api_key", self.session_id
49+
)
50+
51+
# Add content with REDACTED marker to buffer
52+
self.context.buffer.append(f"Here is the REDACTED<${encrypted}> in text")
53+
54+
# Process a chunk
55+
result = await self.step.process_chunk(
56+
create_model_response("more text"), self.context, self.input_context
57+
)
58+
59+
# Verify unredaction
60+
assert result is not None
61+
assert result.choices[0].delta.content == "Here is the secret_value in text"
62+
63+
@pytest.mark.asyncio
64+
async def test_partial_marker_buffering(self):
65+
"""Test handling of partial REDACTED markers"""
66+
# Add partial marker to buffer
67+
self.context.buffer.append("Here is REDACTED<$")
68+
69+
# Process a chunk
70+
result = await self.step.process_chunk(
71+
create_model_response("partial"), self.context, self.input_context
72+
)
73+
74+
# Should return None to continue buffering
75+
assert result is None
76+
77+
@pytest.mark.asyncio
78+
async def test_invalid_encrypted_value(self):
79+
"""Test handling of invalid encrypted values"""
80+
# Add content with invalid encrypted value
81+
self.context.buffer.append("Here is REDACTED<$invalid_value> in text")
82+
83+
# Process chunk
84+
result = await self.step.process_chunk(
85+
create_model_response("text"), self.context, self.input_context
86+
)
87+
88+
# Should keep the REDACTED marker for invalid values
89+
assert result is not None
90+
assert result.choices[0].delta.content == "Here is REDACTED<$invalid_value> in text"
91+
92+
@pytest.mark.asyncio
93+
async def test_missing_context(self):
94+
"""Test handling of missing input context or secrets manager"""
95+
# Test with None input context
96+
with pytest.raises(ValueError, match="Input context not found"):
97+
await self.step.process_chunk(create_model_response("text"), self.context, None)
98+
99+
# Test with missing secrets manager
100+
self.input_context.sensitive.manager = None
101+
with pytest.raises(ValueError, match="Secrets manager not found in input context"):
102+
await self.step.process_chunk(
103+
create_model_response("text"), self.context, self.input_context
104+
)
105+
106+
@pytest.mark.asyncio
107+
async def test_empty_content(self):
108+
"""Test handling of empty content chunks"""
109+
result = await self.step.process_chunk(
110+
create_model_response(""), self.context, self.input_context
111+
)
112+
113+
# Should pass through empty chunks
114+
assert result is not None
115+
assert result.choices[0].delta.content == ""
116+
117+
@pytest.mark.asyncio
118+
async def test_no_markers(self):
119+
"""Test processing of content without any REDACTED markers"""
120+
# Create chunk with content
121+
chunk = create_model_response("Regular text without any markers")
122+
123+
# Process chunk
124+
result = await self.step.process_chunk(chunk, self.context, self.input_context)
125+
126+
# Should pass through unchanged
127+
assert result is not None
128+
assert result.choices[0].delta.content == "Regular text without any markers"
129+
130+
@pytest.mark.asyncio
131+
async def test_wrong_session(self):
132+
"""Test unredaction with wrong session ID"""
133+
# Store secret with one session
134+
encrypted = self.secrets_manager.store_secret(
135+
"secret_value", "test_service", "api_key", "different_session"
136+
)
137+
138+
# Try to unredact with different session
139+
self.context.buffer.append(f"Here is the REDACTED<${encrypted}> in text")
140+
141+
result = await self.step.process_chunk(
142+
create_model_response("text"), self.context, self.input_context
143+
)
144+
145+
# Should keep REDACTED marker when session doesn't match
146+
assert result is not None
147+
assert result.choices[0].delta.content == f"Here is the REDACTED<${encrypted}> in text"

0 commit comments

Comments
 (0)