Skip to content

Commit d1fc143

Browse files
authored
chore: Add more test (#48)
- Increase coverage for existing modules
1 parent dda66d4 commit d1fc143

12 files changed

+396
-33
lines changed

cozepy/conversations/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def create(self, *, messages: List[Message] = None, meta_data: Dict[str, str] =
3434
"""
3535
url = f"{self._base_url}/v1/conversation/create"
3636
body = {
37-
"messages": [i.model_dump() for i in messages] if len(messages) > 0 else [],
37+
"messages": [i.model_dump() for i in messages] if messages and len(messages) > 0 else [],
3838
"meta_data": meta_data,
3939
}
4040
return self._requester.request("post", url, Conversation, body=body)

cozepy/request.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,6 @@ def request(
9494
return None
9595
return model.model_validate(data)
9696

97-
async def arequest(self, method: str, path: str, **kwargs) -> dict:
98-
"""
99-
Send a request to the server with asyncio.
100-
"""
101-
pass
102-
10397
def _make_request(
10498
self,
10599
method: str,

tests/test_auth.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
COZE_COM_BASE_URL,
88
DeviceAuthCode,
99
DeviceOAuthApp,
10+
JWTAuth,
1011
JWTOAuthApp,
1112
OAuthToken,
1213
PKCEOAuthApp,
@@ -69,6 +70,21 @@ def test_refresh_token(self, respx_mock):
6970

7071
@pytest.mark.respx(base_url="https://api.coze.com")
7172
class TestJWTOAuthApp:
73+
def test_jwt_auth(self, respx_mock):
74+
private_key = read_file("testdata/private_key.pem")
75+
mock_token = random_hex(20)
76+
respx_mock.post("/api/permission/oauth2/token").mock(
77+
httpx.Response(
78+
200, content=OAuthToken(access_token=mock_token, expires_in=int(time.time()) + 100).model_dump_json()
79+
)
80+
)
81+
82+
auth = JWTAuth("client id", private_key, "public key id")
83+
84+
assert "Bearer" == auth.token_type
85+
assert mock_token == auth.token
86+
assert mock_token == auth.token # get from cache
87+
7288
def test_get_access_token(self, respx_mock):
7389
private_key = read_file("testdata/private_key.pem")
7490
app = JWTOAuthApp("client id", private_key, "public key id")
@@ -187,6 +203,21 @@ def test_get_access_token(self, respx_mock):
187203

188204
assert token.access_token == mock_token
189205

206+
def test_get_access_token_poll(self, respx_mock):
207+
app = DeviceOAuthApp("client id")
208+
mock_token = random_hex(20)
209+
210+
respx_mock.post("/api/permission/oauth2/token").mock(
211+
httpx.Response(200, json={"error_code": "authorization_pending"})
212+
).mock(
213+
httpx.Response(
214+
200, content=OAuthToken(access_token=mock_token, expires_in=int(time.time())).model_dump_json()
215+
)
216+
)
217+
token = app.get_access_token("https://example.com", True)
218+
219+
assert token.access_token == mock_token
220+
190221
def test_refresh_token(self, respx_mock):
191222
app = DeviceOAuthApp("client id")
192223
mock_token = random_hex(20)

tests/test_chat.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import httpx
2+
import pytest
3+
4+
from cozepy import Chat, ChatEvent, ChatEventType, ChatStatus, Coze, TokenAuth
5+
6+
chat_testdata = Chat(
7+
id="id",
8+
conversation_id="conversation_id",
9+
bot_id="bot_id",
10+
created_at=123,
11+
completed_at=123,
12+
failed_at=123,
13+
meta_data={},
14+
status=ChatStatus.FAILED,
15+
)
16+
17+
chat_stream_testdata = """
18+
event:conversation.chat.created
19+
data:{"id":"7382159487131697202","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","completed_at":1718792949,"last_error":{"code":0,"msg":""},"status":"created","usage":{"token_count":0,"output_count":0,"input_count":0}}
20+
21+
event:conversation.chat.in_progress
22+
data:{"id":"7382159487131697202","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","completed_at":1718792949,"last_error":{"code":0,"msg":""},"status":"in_progress","usage":{"token_count":0,"output_count":0,"input_count":0}}
23+
24+
event:conversation.message.delta
25+
data:{"id":"7382159494123470858","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","role":"assistant","type":"answer","content":"2","content_type":"text","chat_id":"7382159487131697202"}
26+
27+
event:conversation.message.delta
28+
data:{"id":"7382159494123470858","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","role":"assistant","type":"answer","content":"0","content_type":"text","chat_id":"7382159487131697202"}
29+
30+
event:conversation.message.delta
31+
data:{"id":"7382159494123470858","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","role":"assistant","type":"answer","content":"星期三","content_type":"text","chat_id":"7382159487131697202"}
32+
33+
event:conversation.message.delta
34+
data:{"id":"7382159494123470858","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","role":"assistant","type":"answer","content":"。","content_type":"text","chat_id":"7382159487131697202"}
35+
36+
event:conversation.message.completed
37+
data:{"id":"7382159494123470858","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","role":"assistant","type":"answer","content":"2024 年 10 月 1 日是星期三。","content_type":"text","chat_id":"7382159487131697202"}
38+
39+
event:conversation.message.completed
40+
data:{"id":"7382159494123552778","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","role":"assistant","type":"verbose","content":"{\\"msg_type\\":\\"generate_answer_finish\\",\\"data\\":\\"\\",\\"from_module\\":null,\\"from_unit\\":null}","content_type":"text","chat_id":"7382159487131697202"}
41+
42+
event:conversation.chat.completed
43+
data:{"id":"7382159487131697202","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","completed_at":1718792949,"last_error":{"code":0,"msg":""},"status":"completed","usage":{"token_count":633,"output_count":19,"input_count":614}}
44+
45+
event:done
46+
data:"[DONE]"
47+
"""
48+
49+
50+
@pytest.mark.respx(base_url="https://api.coze.com")
51+
class TestConversationMessage:
52+
def test_create(self, respx_mock):
53+
coze = Coze(auth=TokenAuth(token="token"))
54+
55+
respx_mock.post("/v3/chat").mock(httpx.Response(200, json={"data": chat_testdata.model_dump()}))
56+
res = coze.chat.create(bot_id="bot", user_id="user")
57+
58+
assert res
59+
assert res.conversation_id == chat_testdata.conversation_id
60+
61+
def test_stream(self, respx_mock):
62+
coze = Coze(auth=TokenAuth(token="token"))
63+
64+
respx_mock.post("/v3/chat").mock(httpx.Response(200, content=chat_stream_testdata))
65+
events = list(coze.chat.stream(bot_id="bot", user_id="user"))
66+
67+
assert events
68+
assert len(events) == 9
69+
assert events[0] == ChatEvent(
70+
event=ChatEventType.CONVERSATION_CHAT_CREATED,
71+
chat=Chat(
72+
id="7382159487131697202",
73+
conversation_id="7381473525342978089",
74+
bot_id="7379462189365198898",
75+
created_at=None,
76+
completed_at=1718792949,
77+
failed_at=None,
78+
meta_data=None,
79+
status=ChatStatus.CREATED,
80+
),
81+
)
82+
assert events[len(events) - 1].event == ChatEventType.CONVERSATION_CHAT_COMPLETED
83+
84+
def test_retrieve(self, respx_mock):
85+
coze = Coze(auth=TokenAuth(token="token"))
86+
87+
respx_mock.post("/v3/chat/retrieve").mock(httpx.Response(200, json={"data": chat_testdata.model_dump()}))
88+
res = coze.chat.retrieve(conversation_id="conversation", chat_id="chat")
89+
90+
assert res
91+
assert res.conversation_id == chat_testdata.conversation_id
92+
93+
def test_submit_tool_outputs_not_stream(self, respx_mock):
94+
coze = Coze(auth=TokenAuth(token="token"))
95+
96+
respx_mock.post("/v3/chat/submit_tool_outputs").mock(
97+
httpx.Response(200, json={"data": chat_testdata.model_dump()})
98+
)
99+
res = coze.chat.submit_tool_outputs(
100+
conversation_id="conversation", chat_id="chat", tool_outputs=[], stream=False
101+
)
102+
103+
assert res
104+
assert res.conversation_id == chat_testdata.conversation_id
105+
106+
def test_submit_tool_outputs_stream(self, respx_mock):
107+
coze = Coze(auth=TokenAuth(token="token"))
108+
109+
respx_mock.post("/v3/chat/submit_tool_outputs").mock(httpx.Response(200, content=chat_stream_testdata))
110+
events = list(
111+
coze.chat.submit_tool_outputs(conversation_id="conversation", chat_id="chat", tool_outputs=[], stream=True)
112+
)
113+
114+
assert events
115+
assert len(events) == 9
116+
assert events[0] == ChatEvent(
117+
event=ChatEventType.CONVERSATION_CHAT_CREATED,
118+
chat=Chat(
119+
id="7382159487131697202",
120+
conversation_id="7381473525342978089",
121+
bot_id="7379462189365198898",
122+
created_at=None,
123+
completed_at=1718792949,
124+
failed_at=None,
125+
meta_data=None,
126+
status=ChatStatus.CREATED,
127+
),
128+
)
129+
assert events[len(events) - 1].event == ChatEventType.CONVERSATION_CHAT_COMPLETED
130+
131+
def test_cancel(self, respx_mock):
132+
coze = Coze(auth=TokenAuth(token="token"))
133+
134+
respx_mock.post("/v3/chat/cancel").mock(httpx.Response(200, json={"data": chat_testdata.model_dump()}))
135+
res = coze.chat.cancel(conversation_id="conversation", chat_id="chat")
136+
137+
assert res
138+
assert res.conversation_id == chat_testdata.conversation_id

tests/test_chat_message.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import httpx
2+
import pytest
3+
4+
from cozepy import Coze, Message, TokenAuth
5+
6+
7+
@pytest.mark.respx(base_url="https://api.coze.com")
8+
class TestChatMessage:
9+
def test_create(self, respx_mock):
10+
coze = Coze(auth=TokenAuth(token="token"))
11+
12+
msg = Message.user_text_message("hi")
13+
msg2 = Message.user_text_message("hey")
14+
respx_mock.post("/v3/chat/message/list").mock(
15+
httpx.Response(
16+
200,
17+
json={"data": [msg.model_dump(), msg2.model_dump()]},
18+
)
19+
)
20+
21+
message_list = coze.chat.messages.list(conversation_id="conversation id", chat_id="chat id")
22+
assert message_list
23+
assert message_list[0].content == msg.content

tests/test_conversation.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import httpx
2+
import pytest
3+
4+
from cozepy import Conversation, Coze, TokenAuth
5+
6+
7+
@pytest.mark.respx(base_url="https://api.coze.com")
8+
class TestConversation:
9+
def test_create(self, respx_mock):
10+
coze = Coze(auth=TokenAuth(token="token"))
11+
12+
conversation = Conversation(id="id", created_at=1, meta_data={})
13+
respx_mock.post("/v1/conversation/create").mock(httpx.Response(200, json={"data": conversation.model_dump()}))
14+
15+
res = coze.conversations.create()
16+
assert res
17+
assert res.id == conversation.id
18+
19+
def test_retrieve(self, respx_mock):
20+
coze = Coze(auth=TokenAuth(token="token"))
21+
22+
conversation = Conversation(id="id", created_at=1, meta_data={})
23+
respx_mock.get("/v1/conversation/retrieve").mock(httpx.Response(200, json={"data": conversation.model_dump()}))
24+
25+
res = coze.conversations.retrieve(conversation_id="id")
26+
assert res
27+
assert res.id == conversation.id

tests/test_conversation_message.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class TestConversationMessage:
99
def test_create(self, respx_mock):
1010
coze = Coze(auth=TokenAuth(token="token"))
1111

12-
msg = Message.user_text_message("hi")
12+
msg = Message.assistant_text_message("hi")
1313
respx_mock.post("/v1/conversation/message/create").mock(httpx.Response(200, json={"data": msg.model_dump()}))
1414

1515
message = coze.conversations.messages.create(

tests/test_exception.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from cozepy import CozeAPIError
1+
from cozepy import CozeAPIError, CozeEventError, CozePKCEAuthError
22

33

4-
def test_coze_api_error():
4+
def test_coze_error():
55
err = CozeAPIError(1, "msg", "logid")
66
assert err.code == 1
77
assert err.msg == "msg"
@@ -13,3 +13,12 @@ def test_coze_api_error():
1313
assert err.msg == "msg"
1414
assert err.logid == "logid"
1515
assert str(err) == "msg: msg, logid: logid"
16+
17+
err = CozePKCEAuthError("authorization_pending")
18+
assert err.error == "authorization_pending"
19+
20+
err = CozeEventError("event", "xxx")
21+
assert str(err) == "invalid event, field: event, data: xxx"
22+
23+
err = CozeEventError("", "xxx")
24+
assert str(err) == "invalid event, data: xxx"

tests/test_knowledge_documents.py

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,35 @@
1515
TokenAuth,
1616
)
1717

18+
document_testdata = Document.model_validate(
19+
{
20+
"document_id": "str",
21+
"char_count": 1,
22+
"create_time": 1,
23+
"update_time": 1,
24+
"format_type": DocumentFormatType.DOCUMENT,
25+
"hit_count": 1,
26+
"name": "str",
27+
"size": 1,
28+
"slice_count": 1,
29+
"source_type": DocumentSourceType.LOCAL_FILE,
30+
"status": DocumentStatus.FAILED,
31+
"type": "str",
32+
"update_interval": 1,
33+
"update_type": DocumentUpdateType.AUTO_UPDATE,
34+
}
35+
)
36+
1837

1938
@pytest.mark.respx(base_url="https://api.coze.com")
2039
class TestKnowledgeDocuments:
21-
def test_create(self, respx_mock):
40+
def test_create_web_auto_update(self, respx_mock):
2241
coze = Coze(auth=TokenAuth(token="token"))
2342

2443
respx_mock.post("/open_api/knowledge/document/create").mock(
2544
httpx.Response(
2645
200,
27-
json={
28-
"document_infos": [
29-
Document.model_validate(
30-
{
31-
"document_id": "str",
32-
"char_count": 1,
33-
"create_time": 1,
34-
"update_time": 1,
35-
"format_type": DocumentFormatType.DOCUMENT,
36-
"hit_count": 1,
37-
"name": "str",
38-
"size": 1,
39-
"slice_count": 1,
40-
"source_type": DocumentSourceType.LOCAL_FILE,
41-
"status": DocumentStatus.FAILED,
42-
"type": "str",
43-
"update_interval": 1,
44-
"update_type": DocumentUpdateType.AUTO_UPDATE,
45-
}
46-
).model_dump()
47-
]
48-
},
46+
json={"document_infos": [document_testdata.model_dump()]},
4947
)
5048
)
5149

@@ -63,6 +61,30 @@ def test_create(self, respx_mock):
6361
assert documents
6462
assert len(documents) == 1
6563

64+
def test_create_local_custom(self, respx_mock):
65+
coze = Coze(auth=TokenAuth(token="token"))
66+
67+
respx_mock.post("/open_api/knowledge/document/create").mock(
68+
httpx.Response(
69+
200,
70+
json={"document_infos": [document_testdata.model_dump()]},
71+
)
72+
)
73+
74+
documents = coze.knowledge.documents.create(
75+
dataset_id="dataset_id",
76+
document_bases=[
77+
DocumentBase(
78+
name="name",
79+
source_info=DocumentSourceInfo.from_local_file("content"),
80+
update_rule=DocumentUpdateRule.no_auto_update(),
81+
),
82+
],
83+
chunk_strategy=DocumentChunkStrategy.custom(1, ",", False, True),
84+
)
85+
assert documents
86+
assert len(documents) == 1
87+
6688
def test_update(self, respx_mock):
6789
coze = Coze(auth=TokenAuth(token="token"))
6890

tests/test_log.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import logging
2+
3+
import pytest
4+
5+
from cozepy import setup_logging
6+
7+
8+
def test_log():
9+
with pytest.raises(ValueError):
10+
setup_logging(123)
11+
12+
setup_logging(logging.DEBUG)

0 commit comments

Comments
 (0)