feat: make all memories public by default
All memories are now visible to all users in recall/list/count queries. Each memory still has an owner (user_id) who retains exclusive delete rights. This removes the need for explicit sharing — wizard and emo automatically see each other's memories. Changes: - recall/list: single query without user_id filter, added owner field - count: counts all memories globally - REST categories/tags: show all users' data - Delete/update: unchanged (owner-only or write-share) - Sync: unchanged (stays user-scoped) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
03681aae49
commit
43a5513f6c
2 changed files with 58 additions and 215 deletions
|
|
@ -213,16 +213,15 @@ async def recall_memories(body: MemoryRecall, user: AuthUser = Depends(get_curre
|
|||
params.append(body.category)
|
||||
|
||||
async with pool.acquire() as conn:
|
||||
# Own memories (AND-match)
|
||||
# All memories (public by default) — AND-match
|
||||
rows = await conn.fetch(
|
||||
f"""
|
||||
SELECT id, content, category, tags, importance, is_sensitive,
|
||||
ts_rank(search_vector, query) AS rank,
|
||||
created_at, updated_at,
|
||||
NULL::text AS shared_by, NULL::text AS share_permission
|
||||
created_at, updated_at, user_id AS owner,
|
||||
CASE WHEN user_id = $1 THEN NULL ELSE user_id END AS shared_by
|
||||
FROM memories, plainto_tsquery('english', $2) query
|
||||
WHERE user_id = $1
|
||||
AND deleted_at IS NULL
|
||||
WHERE deleted_at IS NULL
|
||||
AND (search_vector @@ query OR $2 = '')
|
||||
{category_filter}
|
||||
ORDER BY {order_clause}
|
||||
|
|
@ -231,64 +230,9 @@ async def recall_memories(body: MemoryRecall, user: AuthUser = Depends(get_curre
|
|||
*params,
|
||||
)
|
||||
|
||||
# Individually shared memories
|
||||
shared_rows = await conn.fetch(
|
||||
f"""
|
||||
SELECT m.id, m.content, m.category, m.tags, m.importance, m.is_sensitive,
|
||||
ts_rank(m.search_vector, query) AS rank,
|
||||
m.created_at, m.updated_at,
|
||||
m.user_id AS shared_by, ms.permission AS share_permission
|
||||
FROM memories m
|
||||
JOIN memory_shares ms ON ms.memory_id = m.id,
|
||||
plainto_tsquery('english', $2) query
|
||||
WHERE ms.shared_with = $1
|
||||
AND m.deleted_at IS NULL
|
||||
AND (m.search_vector @@ query OR $2 = '')
|
||||
{category_filter}
|
||||
ORDER BY {order_clause}
|
||||
LIMIT $3
|
||||
""",
|
||||
*params,
|
||||
)
|
||||
all_rows = list(rows)
|
||||
|
||||
# Tag-shared memories
|
||||
tag_shared_rows = await conn.fetch(
|
||||
f"""
|
||||
SELECT DISTINCT ON (m.id)
|
||||
m.id, m.content, m.category, m.tags, m.importance, m.is_sensitive,
|
||||
ts_rank(m.search_vector, query) AS rank,
|
||||
m.created_at, m.updated_at,
|
||||
m.user_id AS shared_by, ts.permission AS share_permission
|
||||
FROM memories m
|
||||
JOIN tag_shares ts ON ts.owner_id = m.user_id,
|
||||
plainto_tsquery('english', $2) query
|
||||
WHERE ts.shared_with = $1
|
||||
AND m.deleted_at IS NULL
|
||||
AND (m.search_vector @@ query OR $2 = '')
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM unnest(string_to_array(m.tags, ',')) t
|
||||
WHERE trim(t) = ts.tag
|
||||
)
|
||||
{category_filter}
|
||||
ORDER BY m.id
|
||||
LIMIT $3
|
||||
""",
|
||||
*params,
|
||||
)
|
||||
|
||||
# Merge and deduplicate
|
||||
seen_ids: set[int] = set()
|
||||
all_rows = []
|
||||
for row in list(rows) + list(shared_rows) + list(tag_shared_rows):
|
||||
if row["id"] not in seen_ids:
|
||||
seen_ids.add(row["id"])
|
||||
all_rows.append(row)
|
||||
|
||||
# Sort merged results by importance desc and trim
|
||||
all_rows.sort(key=lambda r: r["importance"], reverse=True)
|
||||
all_rows = all_rows[:body.limit]
|
||||
|
||||
# If AND-match returned too few results, broaden to OR-match (own memories only)
|
||||
# If AND-match returned too few results, broaden to OR-match
|
||||
if len(all_rows) < body.limit and query_text:
|
||||
words = query_text.split()
|
||||
if len(words) > 1:
|
||||
|
|
@ -298,15 +242,15 @@ async def recall_memories(body: MemoryRecall, user: AuthUser = Depends(get_curre
|
|||
if body.category:
|
||||
or_cat_filter = "AND category = $4"
|
||||
or_params.append(body.category)
|
||||
seen_ids = {r["id"] for r in all_rows}
|
||||
or_rows = await conn.fetch(
|
||||
f"""
|
||||
SELECT id, content, category, tags, importance, is_sensitive,
|
||||
ts_rank(search_vector, query) AS rank,
|
||||
created_at, updated_at,
|
||||
NULL::text AS shared_by, NULL::text AS share_permission
|
||||
created_at, updated_at, user_id AS owner,
|
||||
CASE WHEN user_id = $1 THEN NULL ELSE user_id END AS shared_by
|
||||
FROM memories, to_tsquery('english', $2) query
|
||||
WHERE user_id = $1
|
||||
AND deleted_at IS NULL
|
||||
WHERE deleted_at IS NULL
|
||||
AND search_vector @@ query
|
||||
{or_cat_filter}
|
||||
ORDER BY {order_clause}
|
||||
|
|
@ -331,10 +275,10 @@ async def recall_memories(body: MemoryRecall, user: AuthUser = Depends(get_curre
|
|||
"importance": row["importance"],
|
||||
"is_sensitive": row["is_sensitive"],
|
||||
"rank": float(row["rank"]),
|
||||
"owner": row["owner"],
|
||||
"created_at": row["created_at"].isoformat(),
|
||||
"updated_at": row["updated_at"].isoformat(),
|
||||
"shared_by": row["shared_by"],
|
||||
"share_permission": row["share_permission"],
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -351,10 +295,10 @@ async def list_memories(
|
|||
) -> dict[str, Any]:
|
||||
pool = await get_pool()
|
||||
|
||||
# Build WHERE clauses dynamically
|
||||
where_clauses = ["user_id = $1", "deleted_at IS NULL"]
|
||||
count_params: list[Any] = [user.user_id]
|
||||
param_idx = 2
|
||||
# Build WHERE clauses dynamically — all memories are public
|
||||
where_clauses = ["deleted_at IS NULL"]
|
||||
count_params: list[Any] = []
|
||||
param_idx = 1
|
||||
|
||||
if category:
|
||||
where_clauses.append(f"category = ${param_idx}")
|
||||
|
|
@ -373,7 +317,7 @@ async def list_memories(
|
|||
|
||||
params: list[Any] = [*count_params, limit, offset]
|
||||
query = f"""
|
||||
SELECT id, content, category, tags, importance, is_sensitive, created_at, updated_at
|
||||
SELECT id, content, category, tags, importance, is_sensitive, created_at, updated_at, user_id AS owner
|
||||
FROM memories WHERE {where}
|
||||
ORDER BY importance DESC LIMIT ${param_idx} OFFSET ${param_idx + 1}
|
||||
"""
|
||||
|
|
@ -395,6 +339,7 @@ async def list_memories(
|
|||
"tags": row["tags"],
|
||||
"importance": row["importance"],
|
||||
"is_sensitive": row["is_sensitive"],
|
||||
"owner": row["owner"],
|
||||
"created_at": row["created_at"].isoformat(),
|
||||
"updated_at": row["updated_at"].isoformat(),
|
||||
}
|
||||
|
|
@ -405,30 +350,28 @@ async def list_memories(
|
|||
|
||||
@app.get("/api/categories")
|
||||
async def list_categories(user: AuthUser = Depends(get_current_user)) -> dict[str, Any]:
|
||||
"""Return distinct category values for the current user."""
|
||||
"""Return distinct category values across all users."""
|
||||
pool = await get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
rows = await conn.fetch(
|
||||
"SELECT DISTINCT category FROM memories WHERE user_id = $1 AND deleted_at IS NULL ORDER BY category",
|
||||
user.user_id,
|
||||
"SELECT DISTINCT category FROM memories WHERE deleted_at IS NULL ORDER BY category",
|
||||
)
|
||||
return {"categories": [r["category"] for r in rows]}
|
||||
|
||||
|
||||
@app.get("/api/tags")
|
||||
async def list_tags(user: AuthUser = Depends(get_current_user)) -> dict[str, Any]:
|
||||
"""Return all distinct tags with memory counts for the current user."""
|
||||
"""Return all distinct tags with memory counts across all users."""
|
||||
pool = await get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
rows = await conn.fetch(
|
||||
"""
|
||||
SELECT trim(t) as tag, COUNT(*) as count
|
||||
FROM memories, unnest(string_to_array(tags, ',')) AS t
|
||||
WHERE user_id = $1 AND deleted_at IS NULL AND tags != '' AND tags IS NOT NULL
|
||||
WHERE deleted_at IS NULL AND tags != '' AND tags IS NOT NULL
|
||||
GROUP BY trim(t)
|
||||
ORDER BY count DESC
|
||||
""",
|
||||
user.user_id,
|
||||
)
|
||||
return {"tags": [{"tag": r["tag"], "count": r["count"]} for r in rows]}
|
||||
|
||||
|
|
@ -946,9 +889,10 @@ async def memory_recall(context: str, expanded_query: str = "",
|
|||
f"""
|
||||
SELECT id, content, category, tags, importance, is_sensitive,
|
||||
ts_rank(search_vector, query) AS rank, created_at, updated_at,
|
||||
NULL::text AS shared_by
|
||||
user_id AS owner,
|
||||
CASE WHEN user_id = $1 THEN NULL ELSE user_id END AS shared_by
|
||||
FROM memories, plainto_tsquery('english', $2) query
|
||||
WHERE user_id = $1 AND deleted_at IS NULL
|
||||
WHERE deleted_at IS NULL
|
||||
AND (search_vector @@ query OR $2 = '')
|
||||
{category_filter}
|
||||
ORDER BY {order_clause}
|
||||
|
|
@ -957,34 +901,8 @@ async def memory_recall(context: str, expanded_query: str = "",
|
|||
*params,
|
||||
)
|
||||
|
||||
# Also fetch shared memories (individual + tag-based)
|
||||
shared_rows = await conn.fetch(
|
||||
"""
|
||||
SELECT DISTINCT ON (m.id) m.id, m.content, m.category, m.tags, m.importance,
|
||||
m.is_sensitive, ts_rank(m.search_vector, query) AS rank,
|
||||
m.created_at, m.updated_at, m.user_id AS shared_by
|
||||
FROM memories m, plainto_tsquery('english', $2) query
|
||||
WHERE m.deleted_at IS NULL
|
||||
AND (m.search_vector @@ query OR $2 = '')
|
||||
AND m.user_id != $1
|
||||
AND (
|
||||
EXISTS (SELECT 1 FROM memory_shares ms WHERE ms.memory_id = m.id AND ms.shared_with = $1)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM tag_shares ts
|
||||
WHERE ts.owner_id = m.user_id AND ts.shared_with = $1
|
||||
AND EXISTS (SELECT 1 FROM unnest(string_to_array(m.tags, ',')) t WHERE trim(t) = ts.tag)
|
||||
)
|
||||
)
|
||||
ORDER BY m.id
|
||||
LIMIT $3
|
||||
""",
|
||||
*params,
|
||||
)
|
||||
|
||||
seen_ids = set()
|
||||
results = []
|
||||
for row in rows:
|
||||
seen_ids.add(row["id"])
|
||||
c = row["content"]
|
||||
if row["is_sensitive"]:
|
||||
c = f"[SENSITIVE - use secret_get(id={row['id']})]"
|
||||
|
|
@ -992,27 +910,14 @@ async def memory_recall(context: str, expanded_query: str = "",
|
|||
"id": row["id"], "content": c, "category": row["category"],
|
||||
"tags": row["tags"], "importance": row["importance"],
|
||||
"rank": float(row["rank"]),
|
||||
"owner": row["owner"],
|
||||
"created_at": row["created_at"].isoformat(),
|
||||
"updated_at": row["updated_at"].isoformat(),
|
||||
}
|
||||
if row["shared_by"]:
|
||||
entry["shared_by"] = row["shared_by"]
|
||||
results.append(entry)
|
||||
|
||||
for row in shared_rows:
|
||||
if row["id"] in seen_ids:
|
||||
continue
|
||||
seen_ids.add(row["id"])
|
||||
c = row["content"]
|
||||
if row["is_sensitive"]:
|
||||
c = f"[SENSITIVE - use secret_get(id={row['id']})]"
|
||||
results.append({
|
||||
"id": row["id"], "content": c, "category": row["category"],
|
||||
"tags": row["tags"], "importance": row["importance"],
|
||||
"rank": float(row["rank"]),
|
||||
"shared_by": row["shared_by"],
|
||||
"created_at": row["created_at"].isoformat(),
|
||||
"updated_at": row["updated_at"].isoformat(),
|
||||
})
|
||||
|
||||
return json.dumps({"memories": results})
|
||||
|
||||
|
||||
|
|
@ -1020,81 +925,30 @@ async def memory_recall(context: str, expanded_query: str = "",
|
|||
async def memory_list(category: str | None = None, limit: int = 20) -> str:
|
||||
"""List stored memories."""
|
||||
pool = await get_pool()
|
||||
user_id = _current_user.get()
|
||||
|
||||
if category:
|
||||
query = """SELECT id, content, category, tags, importance, is_sensitive, created_at, updated_at
|
||||
FROM memories WHERE user_id = $1 AND deleted_at IS NULL AND category = $2
|
||||
ORDER BY importance DESC LIMIT $3"""
|
||||
params: list[Any] = [user_id, category, limit]
|
||||
else:
|
||||
query = """SELECT id, content, category, tags, importance, is_sensitive, created_at, updated_at
|
||||
FROM memories WHERE user_id = $1 AND deleted_at IS NULL
|
||||
query = """SELECT id, content, category, tags, importance, is_sensitive, created_at, updated_at, user_id AS owner
|
||||
FROM memories WHERE deleted_at IS NULL AND category = $1
|
||||
ORDER BY importance DESC LIMIT $2"""
|
||||
params = [user_id, limit]
|
||||
|
||||
if category:
|
||||
shared_query = """
|
||||
SELECT DISTINCT ON (m.id) m.id, m.content, m.category, m.tags, m.importance,
|
||||
m.is_sensitive, m.created_at, m.updated_at, m.user_id AS shared_by
|
||||
FROM memories m
|
||||
WHERE m.deleted_at IS NULL AND m.category = $2 AND m.user_id != $1
|
||||
AND (
|
||||
EXISTS (SELECT 1 FROM memory_shares ms WHERE ms.memory_id = m.id AND ms.shared_with = $1)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM tag_shares ts
|
||||
WHERE ts.owner_id = m.user_id AND ts.shared_with = $1
|
||||
AND EXISTS (SELECT 1 FROM unnest(string_to_array(m.tags, ',')) t WHERE trim(t) = ts.tag)
|
||||
)
|
||||
)
|
||||
ORDER BY m.id LIMIT $3"""
|
||||
shared_params: list[Any] = [user_id, category, limit]
|
||||
params: list[Any] = [category, limit]
|
||||
else:
|
||||
shared_query = """
|
||||
SELECT DISTINCT ON (m.id) m.id, m.content, m.category, m.tags, m.importance,
|
||||
m.is_sensitive, m.created_at, m.updated_at, m.user_id AS shared_by
|
||||
FROM memories m
|
||||
WHERE m.deleted_at IS NULL AND m.user_id != $1
|
||||
AND (
|
||||
EXISTS (SELECT 1 FROM memory_shares ms WHERE ms.memory_id = m.id AND ms.shared_with = $1)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM tag_shares ts
|
||||
WHERE ts.owner_id = m.user_id AND ts.shared_with = $1
|
||||
AND EXISTS (SELECT 1 FROM unnest(string_to_array(m.tags, ',')) t WHERE trim(t) = ts.tag)
|
||||
)
|
||||
)
|
||||
ORDER BY m.id LIMIT $2"""
|
||||
shared_params = [user_id, limit]
|
||||
query = """SELECT id, content, category, tags, importance, is_sensitive, created_at, updated_at, user_id AS owner
|
||||
FROM memories WHERE deleted_at IS NULL
|
||||
ORDER BY importance DESC LIMIT $1"""
|
||||
params = [limit]
|
||||
|
||||
async with pool.acquire() as conn:
|
||||
rows = await conn.fetch(query, *params)
|
||||
shared_rows = await conn.fetch(shared_query, *shared_params)
|
||||
|
||||
seen_ids = set()
|
||||
results = []
|
||||
for row in rows:
|
||||
seen_ids.add(row["id"])
|
||||
c = row["content"]
|
||||
if row["is_sensitive"]:
|
||||
c = f"[SENSITIVE - use secret_get(id={row['id']})]"
|
||||
results.append({
|
||||
"id": row["id"], "content": c, "category": row["category"],
|
||||
"tags": row["tags"], "importance": row["importance"],
|
||||
"created_at": row["created_at"].isoformat(),
|
||||
"updated_at": row["updated_at"].isoformat(),
|
||||
})
|
||||
|
||||
for row in shared_rows:
|
||||
if row["id"] in seen_ids:
|
||||
continue
|
||||
seen_ids.add(row["id"])
|
||||
c = row["content"]
|
||||
if row["is_sensitive"]:
|
||||
c = f"[SENSITIVE - use secret_get(id={row['id']})]"
|
||||
results.append({
|
||||
"id": row["id"], "content": c, "category": row["category"],
|
||||
"tags": row["tags"], "importance": row["importance"],
|
||||
"shared_by": row["shared_by"],
|
||||
"owner": row["owner"],
|
||||
"created_at": row["created_at"].isoformat(),
|
||||
"updated_at": row["updated_at"].isoformat(),
|
||||
})
|
||||
|
|
@ -1131,9 +985,8 @@ async def memory_delete(memory_id: int) -> str:
|
|||
async def memory_count() -> str:
|
||||
"""Count total memories."""
|
||||
pool = await get_pool()
|
||||
user_id = _current_user.get()
|
||||
async with pool.acquire() as conn:
|
||||
count = await conn.fetchval("SELECT COUNT(*) FROM memories WHERE user_id = $1 AND deleted_at IS NULL", user_id)
|
||||
count = await conn.fetchval("SELECT COUNT(*) FROM memories WHERE deleted_at IS NULL")
|
||||
return json.dumps({"count": count})
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ def _make_memory_row(**overrides):
|
|||
"created_at": now,
|
||||
"updated_at": now,
|
||||
"deleted_at": None,
|
||||
"owner": "testuser",
|
||||
"shared_by": None,
|
||||
"share_permission": None,
|
||||
}
|
||||
|
|
@ -139,14 +140,11 @@ async def test_store_memory_creates_record_with_user_id(client):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_recall_returns_only_user_memories(client):
|
||||
async def test_recall_returns_all_memories(client):
|
||||
ac, conn, app_mod = client
|
||||
# recall calls fetch 3 times: own, shared, tag-shared; plus OR-fallback if < limit
|
||||
conn.fetch.side_effect = [
|
||||
[_make_memory_row(id=1, content="user memory", is_sensitive=False)], # own
|
||||
[], # individually shared
|
||||
[], # tag-shared
|
||||
[], # OR-match fallback
|
||||
# recall now runs a single query (all memories are public)
|
||||
conn.fetch.return_value = [
|
||||
_make_memory_row(id=1, content="user memory", is_sensitive=False, owner="testuser", shared_by=None),
|
||||
]
|
||||
|
||||
async with ac:
|
||||
|
|
@ -161,19 +159,14 @@ async def test_recall_returns_only_user_memories(client):
|
|||
results = data["memories"]
|
||||
assert len(results) == 1
|
||||
assert results[0]["content"] == "user memory"
|
||||
|
||||
# Verify query includes user_id filter
|
||||
call_args = conn.fetch.call_args
|
||||
assert call_args[0][1] == "testuser"
|
||||
assert results[0]["owner"] == "testuser"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_recall_redacts_sensitive_memories(client):
|
||||
ac, conn, app_mod = client
|
||||
conn.fetch.side_effect = [
|
||||
[_make_memory_row(id=5, content="[REDACTED]", is_sensitive=True)], # own
|
||||
[], # individually shared
|
||||
[], # tag-shared
|
||||
conn.fetch.return_value = [
|
||||
_make_memory_row(id=5, content="[REDACTED]", is_sensitive=True, owner="testuser", shared_by=None),
|
||||
]
|
||||
|
||||
async with ac:
|
||||
|
|
@ -191,11 +184,11 @@ async def test_recall_redacts_sensitive_memories(client):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_returns_only_user_memories(client):
|
||||
async def test_list_returns_all_memories(client):
|
||||
ac, conn, app_mod = client
|
||||
conn.fetch.return_value = [
|
||||
_make_memory_row(id=1, content="mem1"),
|
||||
_make_memory_row(id=2, content="mem2"),
|
||||
_make_memory_row(id=1, content="mem1", owner="testuser"),
|
||||
_make_memory_row(id=2, content="mem2", owner="otheruser"),
|
||||
]
|
||||
|
||||
async with ac:
|
||||
|
|
@ -208,10 +201,8 @@ async def test_list_returns_only_user_memories(client):
|
|||
data = resp.json()
|
||||
results = data["memories"]
|
||||
assert len(results) == 2
|
||||
|
||||
# Verify user_id filter
|
||||
call_args = conn.fetch.call_args
|
||||
assert call_args[0][1] == "testuser"
|
||||
assert results[0]["owner"] == "testuser"
|
||||
assert results[1]["owner"] == "otheruser"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -601,15 +592,14 @@ async def test_my_shares_returns_outgoing_shares(client):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_recall_includes_shared_memories(client):
|
||||
"""POST /api/memories/recall includes shared memories with shared_by field."""
|
||||
async def test_recall_includes_all_users_memories(client):
|
||||
"""POST /api/memories/recall returns all users' memories with owner field."""
|
||||
ac, conn, app_mod = client
|
||||
# recall calls fetch multiple times: own, shared, tag-shared, OR-fallback
|
||||
conn.fetch.side_effect = [
|
||||
[_make_memory_row(id=1, content="own memory", user_id="testuser", shared_by=None)], # own
|
||||
[_make_memory_row(id=2, content="shared memory", user_id="owner1", shared_by="owner1")], # shared
|
||||
[_make_memory_row(id=3, content="tag shared", user_id="owner2", shared_by="owner2")], # tag-shared
|
||||
[], # OR-fallback
|
||||
# Single query returns all memories (public by default)
|
||||
conn.fetch.return_value = [
|
||||
_make_memory_row(id=1, content="own memory", owner="testuser", shared_by=None),
|
||||
_make_memory_row(id=2, content="other memory", owner="owner1", shared_by="owner1"),
|
||||
_make_memory_row(id=3, content="another memory", owner="owner2", shared_by="owner2"),
|
||||
]
|
||||
|
||||
async with ac:
|
||||
|
|
@ -623,10 +613,10 @@ async def test_recall_includes_shared_memories(client):
|
|||
data = resp.json()
|
||||
results = data["memories"]
|
||||
assert len(results) == 3
|
||||
# Check that shared_by field appears in shared memories
|
||||
assert results[0]["owner"] == "testuser"
|
||||
assert results[0]["shared_by"] is None
|
||||
assert results[1]["owner"] == "owner1"
|
||||
assert results[1]["shared_by"] == "owner1"
|
||||
assert results[2]["shared_by"] == "owner2"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue