feat: get peer cards endpoint (#209)

* feat: get peer cards endpoint

* fix: rm try/catch

* refactor: POST -> GET

* fix: /peer-cards -> /peer-card

* fix: /peer-cards -> /peer-card

---------

Co-authored-by: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com>
This commit is contained in:
Rajat Ahuja 2025-09-24 12:56:45 -04:00 committed by GitHub
parent 00396976f8
commit fb66630142
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 123 additions and 6 deletions

View File

@ -1,12 +1,7 @@
import logging
from collections.abc import AsyncGenerator, AsyncIterator
from fastapi import (
APIRouter,
Body,
Depends,
Path,
)
from fastapi import APIRouter, Body, Depends, Path, Query
from fastapi.exceptions import HTTPException
from fastapi.responses import StreamingResponse
from fastapi_pagination import Page
@ -239,6 +234,34 @@ async def get_working_representation(
raise ResourceNotFoundException("Peer or session not found") from e
@router.get(
"/{peer_id}/card",
response_model=schemas.PeerCardResponse,
dependencies=[
Depends(require_auth(workspace_name="workspace_id", peer_name="peer_id"))
],
)
async def get_peer_card(
workspace_id: str = Path(..., description="ID of the workspace"),
peer_id: str = Path(..., description="ID of the observer peer"),
target: str | None = Query(
None,
description="The peer whose card to retrieve. If not provided, returns the observer's own card",
),
db: AsyncSession = db,
):
"""Get a peer card for a specific peer relationship.
Returns the peer card that the observer peer has for the target peer if it exists.
If no target is specified, returns the observer's own peer card.
"""
# If no target specified, get the observer's own card
target_peer = target if target is not None else peer_id
peer_card = await crud.get_peer_card(db, workspace_id, target_peer, peer_id)
return schemas.PeerCardResponse(peer_card=peer_card)
@router.post(
"/{peer_id}/search",
response_model=list[schemas.Message],

View File

@ -104,6 +104,12 @@ class PeerRepresentationGet(BaseModel):
)
class PeerCardResponse(BaseModel):
peer_card: list[str] | None = Field(
None, description="The peer card content, or None if not found"
)
class PeerConfig(BaseModel):
observe_me: bool = Field(
default=True,

View File

@ -1,10 +1,12 @@
from typing import Any
from unittest.mock import AsyncMock, patch
import pytest
from fastapi.testclient import TestClient
from nanoid import generate as generate_nanoid
from sqlalchemy.ext.asyncio import AsyncSession
from src import crud
from src.models import Peer, Workspace
@ -569,3 +571,89 @@ def test_update_peer_all_fields(
data = response.json()
assert data["metadata"] == metadata
assert data["configuration"] == configuration
def test_get_peer_card(client: TestClient, sample_data: tuple[Workspace, Peer]):
"""Test the peer cards endpoint"""
test_workspace, observer_peer = sample_data
# Create a second peer (the target/observed peer)
target_peer_name = str(generate_nanoid())
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers",
json={"name": target_peer_name},
)
assert response.status_code == 200
# Test getting observer's own card (should return null initially)
response = client.get(
f"/v2/workspaces/{test_workspace.name}/peers/{observer_peer.name}/card"
)
assert response.status_code == 200
data = response.json()
assert data["peer_card"] is None
# Test getting card for target peer (should return null initially)
response = client.get(
f"/v2/workspaces/{test_workspace.name}/peers/{observer_peer.name}/card",
params={"target": target_peer_name},
)
assert response.status_code == 200
data = response.json()
assert data["peer_card"] is None
@pytest.mark.asyncio
async def test_get_peer_card_with_data(
client: TestClient,
db_session: AsyncSession,
sample_data: tuple[Workspace, Peer],
):
"""Test the peer cards endpoint with actual peer card data"""
test_workspace, observer_peer = sample_data
# Create a second peer (the target/observed peer)
target_peer_name = str(generate_nanoid())
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers",
json={"name": target_peer_name},
)
assert response.status_code == 200
# Set up peer cards using the database directly
# Set a self-card for the observer peer
self_card_content = ["I am a helpful AI assistant", "I enjoy learning about users"]
await crud.set_peer_card(
db_session,
test_workspace.name,
observer_peer.name,
observer_peer.name,
self_card_content,
)
# Set a card for the observer describing the target peer
target_card_content = ["This peer seems friendly", "They ask good questions"]
await crud.set_peer_card(
db_session,
test_workspace.name,
target_peer_name,
observer_peer.name,
target_card_content,
)
# Test getting observer's own card
response = client.get(
f"/v2/workspaces/{test_workspace.name}/peers/{observer_peer.name}/card"
)
assert response.status_code == 200
data = response.json()
assert data["peer_card"] == self_card_content
# Test getting card for target peer
response = client.get(
f"/v2/workspaces/{test_workspace.name}/peers/{observer_peer.name}/card",
params={"target": target_peer_name},
)
assert response.status_code == 200
data = response.json()
assert data["peer_card"] == target_card_content