mirror of
https://github.com/aimingmed/aimingmed-ai.git
synced 2026-02-07 07:43:25 +08:00
Merge pull request #61 from aimingmed/feature/backend-frontend-structure
try
This commit is contained in:
commit
a49935b4d1
@ -1,4 +1,4 @@
|
|||||||
name: Build
|
name: Build + CI
|
||||||
|
|
||||||
# Triggers: Equivalent to ADO trigger block
|
# Triggers: Equivalent to ADO trigger block
|
||||||
on:
|
on:
|
||||||
@ -36,7 +36,7 @@ jobs:
|
|||||||
image_config:
|
image_config:
|
||||||
- IMAGE_NAME: backend-aimingmedai
|
- IMAGE_NAME: backend-aimingmedai
|
||||||
BUILD_CONTEXT: ./app/backend
|
BUILD_CONTEXT: ./app/backend
|
||||||
DOCKERFILE: ./app/backend/Dockerfile
|
DOCKERFILE: ./app/backend/Dockerfile.prod
|
||||||
- IMAGE_NAME: frontend-aimingmedai
|
- IMAGE_NAME: frontend-aimingmedai
|
||||||
BUILD_CONTEXT: ./app/frontend
|
BUILD_CONTEXT: ./app/frontend
|
||||||
DOCKERFILE: ./app/frontend/Dockerfile.test
|
DOCKERFILE: ./app/frontend/Dockerfile.test
|
||||||
@ -73,17 +73,17 @@ jobs:
|
|||||||
testContainerName: tests-aimingmedai
|
testContainerName: tests-aimingmedai
|
||||||
# Pass test environment variables as JSON string
|
# Pass test environment variables as JSON string
|
||||||
testEnvs: >
|
testEnvs: >
|
||||||
[
|
'[
|
||||||
"FRONTEND_URL=http://frontend:80",
|
"FRONTEND_URL=http://frontend:80",
|
||||||
"BACKEND_URL=http://backend:80",
|
"BACKEND_URL=http://backend:80",
|
||||||
"ENVIRONMENT=dev",
|
"ENVIRONMENT=dev",
|
||||||
"TESTING=1",
|
"TESTING=1",
|
||||||
]
|
]'
|
||||||
# Pass test directories as JSON string
|
# Pass test directories as JSON string
|
||||||
tests: >
|
tests: >
|
||||||
[
|
'[
|
||||||
"tests/integration/backend",
|
"tests/integration/backend",
|
||||||
]
|
]'
|
||||||
# Pass image definitions for compose setup as JSON string
|
# Pass image definitions for compose setup as JSON string
|
||||||
# Sensitive values should be passed via secrets and referenced within the template
|
# Sensitive values should be passed via secrets and referenced within the template
|
||||||
images: >
|
images: >
|
||||||
11
.github/workflows/template_test.yml
vendored
11
.github/workflows/template_test.yml
vendored
@ -146,8 +146,8 @@ jobs:
|
|||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
TEST_DIRS="${{ inputs.tests }}"
|
TEST_DIRS='["tests/integration/backend"]'
|
||||||
TEST_ENVS_JSON="${{ inputs.testEnvs }}"
|
TEST_ENVS_JSON='["FRONTEND_URL=http://frontend:80","BACKEND_URL=http://backend:80","ENVIRONMENT=dev","TESTING=1"]'
|
||||||
RESULTS_PATH="${{ inputs.testResultsPath }}"
|
RESULTS_PATH="${{ inputs.testResultsPath }}"
|
||||||
STAGING_DIR="${{ runner.temp }}/test-results" # Use runner temp dir for results
|
STAGING_DIR="${{ runner.temp }}/test-results" # Use runner temp dir for results
|
||||||
mkdir -p "$STAGING_DIR"
|
mkdir -p "$STAGING_DIR"
|
||||||
@ -156,10 +156,9 @@ jobs:
|
|||||||
ENV_ARGS=""
|
ENV_ARGS=""
|
||||||
if [[ "$TEST_ENVS_JSON" != "[]" ]]; then
|
if [[ "$TEST_ENVS_JSON" != "[]" ]]; then
|
||||||
# Convert JSON array string to individual env vars
|
# Convert JSON array string to individual env vars
|
||||||
IFS=',' read -r -a env_array <<< $(echo "$TEST_ENVS_JSON" | jq -r '.[][]')
|
while IFS= read -r line; do
|
||||||
for env in "${env_array[@]}"; do
|
ENV_ARGS+=" -e \"$line\""
|
||||||
ENV_ARGS+=" -e \"$env\""
|
done <<< $(echo "$TEST_ENVS_JSON" | jq -r '.[]')
|
||||||
done
|
|
||||||
else
|
else
|
||||||
# Add a dummy env var if none are provided, as required by original script logic
|
# Add a dummy env var if none are provided, as required by original script logic
|
||||||
ENV_ARGS+=" -e DUMMY_ENV_TEST_RUN_ID=${{ github.run_id }}"
|
ENV_ARGS+=" -e DUMMY_ENV_TEST_RUN_ID=${{ github.run_id }}"
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
# pull official base image
|
# pull official base image
|
||||||
FROM python:3.11-slim-bullseye AS base
|
FROM python:3.11-slim-bullseye
|
||||||
|
|
||||||
# create directory for the app user
|
# create directory for the app user
|
||||||
RUN mkdir -p /home/app
|
RUN mkdir -p /home/app
|
||||||
@ -16,13 +16,15 @@ WORKDIR $APP_HOME
|
|||||||
# set environment variables
|
# set environment variables
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV ENVIRONMENT=dev
|
||||||
|
ENV TESTING=1
|
||||||
|
|
||||||
# add app
|
# add app
|
||||||
COPY . $APP_HOME
|
COPY . $APP_HOME
|
||||||
|
|
||||||
# install python dependencies
|
# install python dependencies
|
||||||
RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pipenv && rm -rf ~/.cache/pip
|
RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pipenv && rm -rf ~/.cache/pip
|
||||||
RUN pipenv install --deploy
|
RUN pipenv install --deploy --dev
|
||||||
|
|
||||||
# chown all the files to the app user
|
# chown all the files to the app user
|
||||||
RUN chown -R app:app $APP_HOME
|
RUN chown -R app:app $APP_HOME
|
||||||
@ -30,27 +32,10 @@ RUN chown -R app:app $APP_HOME
|
|||||||
# change to the app user
|
# change to the app user
|
||||||
USER app
|
USER app
|
||||||
|
|
||||||
|
# pytest
|
||||||
|
|
||||||
# TEST
|
|
||||||
FROM base AS test
|
|
||||||
|
|
||||||
ENV ENVIRONMENT=dev
|
|
||||||
ENV TESTING=1
|
|
||||||
|
|
||||||
RUN pipenv install --deploy --dev
|
|
||||||
|
|
||||||
# run tests
|
|
||||||
RUN pipenv run pytest tests --disable-warnings
|
RUN pipenv run pytest tests --disable-warnings
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# BUILD
|
|
||||||
FROM base AS builder
|
|
||||||
|
|
||||||
ENV ENVIRONMENT=prod
|
|
||||||
ENV TESTING=0
|
|
||||||
|
|
||||||
# expose the port the app runs on
|
# expose the port the app runs on
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
|
|||||||
@ -5,18 +5,14 @@
|
|||||||
# pull official base image
|
# pull official base image
|
||||||
FROM python:3.11-slim-bookworm AS builder
|
FROM python:3.11-slim-bookworm AS builder
|
||||||
|
|
||||||
|
|
||||||
# set working directory
|
# set working directory
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
# set environment variables
|
# set environment variables
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV ENVIRONMENT=dev
|
||||||
# install system dependencies
|
ENV TESTING=1
|
||||||
# RUN apt-get update && apt-get -y install build-essential \
|
|
||||||
# && apt-get clean \
|
|
||||||
# && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# install python dependencies
|
# install python dependencies
|
||||||
RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pipenv && rm -rf ~/.cache/pip
|
RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pipenv && rm -rf ~/.cache/pip
|
||||||
@ -25,10 +21,10 @@ RUN pipenv install --deploy --dev
|
|||||||
|
|
||||||
# add app
|
# add app
|
||||||
COPY . /usr/src/app
|
COPY . /usr/src/app
|
||||||
# RUN pipenv run pip install black==23.12.1 flake8==7.0.0 isort==5.13.2
|
RUN pipenv run pytest tests --disable-warnings
|
||||||
# RUN pipenv run flake8 .
|
RUN pipenv run flake8 .
|
||||||
# RUN pipenv run black --exclude=migrations . --check
|
RUN pipenv run black --exclude=migrations . --check
|
||||||
# RUN pipenv run isort . --check-only
|
RUN pipenv run isort . --check-only
|
||||||
|
|
||||||
#########
|
#########
|
||||||
# FINAL #
|
# FINAL #
|
||||||
@ -58,9 +54,9 @@ ENV TESTING=0
|
|||||||
|
|
||||||
|
|
||||||
# install python dependencies
|
# install python dependencies
|
||||||
COPY --from=builder /usr/src/app/Pipfile .
|
|
||||||
RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pipenv && rm -rf ~/.cache/pip
|
RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pipenv && rm -rf ~/.cache/pip
|
||||||
RUN pipenv install --deploy --dev
|
COPY --from=builder /usr/src/app/Pipfile .
|
||||||
|
RUN pipenv install --deploy
|
||||||
RUN pipenv run pip install "uvicorn[standard]==0.26.0"
|
RUN pipenv run pip install "uvicorn[standard]==0.26.0"
|
||||||
|
|
||||||
# add app
|
# add app
|
||||||
@ -73,7 +69,7 @@ RUN chown -R app:app $APP_HOME
|
|||||||
USER app
|
USER app
|
||||||
|
|
||||||
# expose the port the app runs on
|
# expose the port the app runs on
|
||||||
EXPOSE 8765
|
EXPOSE 80
|
||||||
|
|
||||||
# run uvicorn
|
# run uvicorn
|
||||||
CMD ["pipenv", "run", "uvicorn", "main:app", "--reload", "--workers", "1", "--host", "0.0.0.0", "--port", "80"]
|
CMD ["pipenv", "run", "uvicorn", "main:app", "--reload", "--workers", "1", "--host", "0.0.0.0", "--port", "80"]
|
||||||
@ -5,8 +5,6 @@ from decouple import config
|
|||||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
||||||
from langchain_deepseek import ChatDeepSeek
|
from langchain_deepseek import ChatDeepSeek
|
||||||
|
|
||||||
from models.adaptive_rag import grading, query, routing
|
|
||||||
|
|
||||||
from .utils import ConnectionManager
|
from .utils import ConnectionManager
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
@ -27,28 +25,36 @@ llm_chat = ChatDeepSeek(
|
|||||||
# Initialize the connection manager
|
# Initialize the connection manager
|
||||||
manager = ConnectionManager()
|
manager = ConnectionManager()
|
||||||
|
|
||||||
|
|
||||||
@router.websocket("/ws")
|
@router.websocket("/ws")
|
||||||
async def websocket_endpoint(websocket: WebSocket):
|
async def websocket_endpoint(websocket: WebSocket):
|
||||||
await manager.connect(websocket)
|
await manager.connect(websocket)
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
data = await websocket.receive_text()
|
data = await websocket.receive_text()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data_json = json.loads(data)
|
data_json = json.loads(data)
|
||||||
if isinstance(data_json, list) and len(data_json) > 0 and 'content' in data_json[0]:
|
if (
|
||||||
async for chunk in llm_chat.astream(data_json[0]['content']):
|
isinstance(data_json, list)
|
||||||
await manager.send_personal_message(json.dumps({"type": "message", "payload": chunk.content}), websocket)
|
and len(data_json) > 0
|
||||||
else:
|
and "content" in data_json[0]
|
||||||
await manager.send_personal_message("Invalid message format", websocket)
|
):
|
||||||
|
async for chunk in llm_chat.astream(data_json[0]["content"]):
|
||||||
|
await manager.send_personal_message(
|
||||||
|
json.dumps({"type": "message", "payload": chunk.content}),
|
||||||
|
websocket,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await manager.send_personal_message(
|
||||||
|
"Invalid message format", websocket
|
||||||
|
)
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
await manager.broadcast("Invalid JSON message")
|
await manager.broadcast("Invalid JSON message")
|
||||||
except WebSocketDisconnect:
|
except WebSocketDisconnect:
|
||||||
manager.disconnect(websocket)
|
manager.disconnect(websocket)
|
||||||
await manager.broadcast("Client disconnected")
|
await manager.broadcast("Client disconnected")
|
||||||
except WebSocketDisconnect:
|
except WebSocketDisconnect:
|
||||||
manager.disconnect(websocket)
|
manager.disconnect(websocket)
|
||||||
await manager.broadcast("Client disconnected")
|
await manager.broadcast("Client disconnected")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -22,4 +22,3 @@ class ConnectionManager:
|
|||||||
json_message = {"type": "message", "payload": message}
|
json_message = {"type": "message", "payload": message}
|
||||||
for connection in self.active_connections:
|
for connection in self.active_connections:
|
||||||
await connection.send_text(json.dumps(json_message))
|
await connection.send_text(json.dumps(json_message))
|
||||||
|
|
||||||
|
|||||||
@ -1,21 +1,19 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import uvicorn
|
from fastapi import FastAPI
|
||||||
from fastapi import Depends, FastAPI
|
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
from api import chatbot, ping
|
from api import chatbot, ping
|
||||||
from config import Settings, get_settings
|
|
||||||
|
|
||||||
log = logging.getLogger("uvicorn")
|
log = logging.getLogger("uvicorn")
|
||||||
|
|
||||||
origins = ["http://localhost:8004"]
|
origins = ["http://localhost:8004"]
|
||||||
|
|
||||||
|
|
||||||
def create_application() -> FastAPI:
|
def create_application() -> FastAPI:
|
||||||
application = FastAPI()
|
application = FastAPI()
|
||||||
application.include_router(ping.router, tags=["ping"])
|
application.include_router(ping.router, tags=["ping"])
|
||||||
application.include_router(
|
application.include_router(chatbot.router, tags=["chatbot"])
|
||||||
chatbot.router, tags=["chatbot"])
|
|
||||||
return application
|
return application
|
||||||
|
|
||||||
|
|
||||||
@ -28,7 +26,3 @@ app.add_middleware(
|
|||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# if __name__ == "__main__":
|
|
||||||
# uvicorn.run("main:app", host="0.0.0.0", port=80, reload=True)
|
|
||||||
@ -8,6 +8,7 @@ class GradeDocuments(BaseModel):
|
|||||||
description="Documents are relevant to the question, 'yes' or 'no'"
|
description="Documents are relevant to the question, 'yes' or 'no'"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class GradeHallucinations(BaseModel):
|
class GradeHallucinations(BaseModel):
|
||||||
"""Binary score for hallucination present in generation answer."""
|
"""Binary score for hallucination present in generation answer."""
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ class GradeHallucinations(BaseModel):
|
|||||||
description="Answer is grounded in the facts, 'yes' or 'no'"
|
description="Answer is grounded in the facts, 'yes' or 'no'"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class GradeAnswer(BaseModel):
|
class GradeAnswer(BaseModel):
|
||||||
"""Binary score to assess answer addresses question."""
|
"""Binary score to assess answer addresses question."""
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,6 @@ from pydantic import BaseModel, Field
|
|||||||
class QueryRequest(BaseModel):
|
class QueryRequest(BaseModel):
|
||||||
query: str = Field(..., description="The question to ask the model")
|
query: str = Field(..., description="The question to ask the model")
|
||||||
|
|
||||||
|
|
||||||
class QueryResponse(BaseModel):
|
class QueryResponse(BaseModel):
|
||||||
response: str = Field(..., description="The model's response")
|
response: str = Field(..., description="The model's response")
|
||||||
|
|
||||||
|
|||||||
2
app/backend/setup.cfg
Normal file
2
app/backend/setup.cfg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[flake8]
|
||||||
|
max-line-length = 119
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import unittest
|
|
||||||
from unittest.mock import AsyncMock, MagicMock
|
|
||||||
|
|
||||||
from fastapi import WebSocket, WebSocketDisconnect
|
|
||||||
|
|
||||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
|
|
||||||
|
|
||||||
from api.chatbot import llm_chat, manager, websocket_endpoint
|
|
||||||
@ -5,11 +5,12 @@ from unittest.mock import AsyncMock, MagicMock
|
|||||||
|
|
||||||
from fastapi import WebSocket
|
from fastapi import WebSocket
|
||||||
|
|
||||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
|
|
||||||
|
|
||||||
from api.utils import ConnectionManager
|
from api.utils import ConnectionManager
|
||||||
|
|
||||||
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
|
||||||
|
|
||||||
|
|
||||||
|
# Test for ConnectionManager class
|
||||||
class TestConnectionManager(unittest.IsolatedAsyncioTestCase):
|
class TestConnectionManager(unittest.IsolatedAsyncioTestCase):
|
||||||
async def asyncSetUp(self):
|
async def asyncSetUp(self):
|
||||||
self.manager = ConnectionManager()
|
self.manager = ConnectionManager()
|
||||||
@ -38,8 +39,13 @@ class TestConnectionManager(unittest.IsolatedAsyncioTestCase):
|
|||||||
self.manager.active_connections = [mock_websocket1, mock_websocket2]
|
self.manager.active_connections = [mock_websocket1, mock_websocket2]
|
||||||
message = "Broadcast message"
|
message = "Broadcast message"
|
||||||
await self.manager.broadcast(message)
|
await self.manager.broadcast(message)
|
||||||
mock_websocket1.send_text.assert_awaited_once_with('{"type": "message", "payload": "Broadcast message"}')
|
mock_websocket1.send_text.assert_awaited_once_with(
|
||||||
mock_websocket2.send_text.assert_awaited_once_with('{"type": "message", "payload": "Broadcast message"}')
|
'{"type": "message", "payload": "Broadcast message"}'
|
||||||
|
)
|
||||||
|
mock_websocket2.send_text.assert_awaited_once_with(
|
||||||
|
'{"type": "message", "payload": "Broadcast message"}'
|
||||||
|
)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
@ -11,7 +11,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./backend
|
context: ./backend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: backend
|
container_name: backend-aimingmedai
|
||||||
platform: linux/amd64
|
platform: linux/amd64
|
||||||
# command: pipenv run uvicorn main:app --reload --workers 1 --host 0.0.0.0 --port 8765
|
# command: pipenv run uvicorn main:app --reload --workers 1 --host 0.0.0.0 --port 8765
|
||||||
volumes:
|
volumes:
|
||||||
@ -26,7 +26,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./frontend
|
context: ./frontend
|
||||||
dockerfile: Dockerfile.test
|
dockerfile: Dockerfile.test
|
||||||
container_name: frontend
|
container_name: frontend-aimingmedai
|
||||||
volumes:
|
volumes:
|
||||||
- ./frontend:/usr/src/app
|
- ./frontend:/usr/src/app
|
||||||
- /usr/src/app/node_modules
|
- /usr/src/app/node_modules
|
||||||
@ -40,6 +40,10 @@ services:
|
|||||||
tests:
|
tests:
|
||||||
build:
|
build:
|
||||||
context: ./tests
|
context: ./tests
|
||||||
|
container_name: tests-aimingmedai
|
||||||
|
# depends_on:
|
||||||
|
# - backend
|
||||||
|
# - frontend
|
||||||
environment:
|
environment:
|
||||||
FRONTEND_URL: http://frontend:80
|
FRONTEND_URL: http://frontend:80
|
||||||
BACKEND_URL: http://backend:80
|
BACKEND_URL: http://backend:80
|
||||||
|
|||||||
@ -11,6 +11,9 @@ evonik-dummy = "*"
|
|||||||
pyrsistent = "*"
|
pyrsistent = "*"
|
||||||
pyjwt = "*"
|
pyjwt = "*"
|
||||||
pydantic = "*"
|
pydantic = "*"
|
||||||
|
websockets = "*"
|
||||||
|
pytest-asyncio = "*"
|
||||||
|
pytest-cov = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
autopep8 = "*"
|
autopep8 = "*"
|
||||||
|
|||||||
29
app/tests/tests/integration/backend/test_frontend_backend.py
Normal file
29
app/tests/tests/integration/backend/test_frontend_backend.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import pytest
|
||||||
|
import subprocess
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
import asyncio
|
||||||
|
import websockets
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_chatbot_integration():
|
||||||
|
# Send a request to the chatbot endpoint
|
||||||
|
url = "ws://backend-aimingmedai:80/ws"
|
||||||
|
data = [{"content": "Hello"}]
|
||||||
|
try:
|
||||||
|
async with websockets.connect(url) as websocket:
|
||||||
|
await websocket.send(json.dumps(data))
|
||||||
|
response = await websocket.recv()
|
||||||
|
assert response is not None
|
||||||
|
try:
|
||||||
|
response_json = json.loads(response)
|
||||||
|
assert "type" in response_json
|
||||||
|
assert "payload" in response_json
|
||||||
|
assert response_json["payload"] == ""
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
assert False, "Invalid JSON response"
|
||||||
|
except Exception as e:
|
||||||
|
pytest.fail(f"Request failed: {e}")
|
||||||
Loading…
x
Reference in New Issue
Block a user