feat: persist isolated vector snapshots and exact retrieval
This commit is contained in:
@@ -0,0 +1,552 @@
|
||||
"""Toy vectors test database mathematics and isolation, never model/domain quality."""
|
||||
|
||||
from dataclasses import replace
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy import func, select, text, update
|
||||
from sqlalchemy.exc import DBAPIError, OperationalError
|
||||
from sqlalchemy.orm import Session
|
||||
from test_indexing import build, publish
|
||||
from test_retrieval import add_item
|
||||
|
||||
from app.models.indexing import Chunk
|
||||
from app.models.retrieval import RetrievalRun
|
||||
from app.models.vectors import ChunkVector, EmbeddingSpace, VectorPublication, VectorVersion
|
||||
from app.services import vectors
|
||||
from app.services.embedding import EmbeddingError, EmbeddingSpec
|
||||
from app.services.embedding_runtime import EmbeddingRuntime, get_embedding_provider
|
||||
from app.services.indexing import locked_state, publish_index
|
||||
|
||||
|
||||
class ToyProvider:
|
||||
spec = EmbeddingSpec("test-only-coordinate-fixture", "a" * 40, 3)
|
||||
callback = None
|
||||
|
||||
def encode(self, texts, *, query=False):
|
||||
if self.callback:
|
||||
self.callback(query)
|
||||
return [[1.0, 0.0, 0.0] if query or "alpha" in t else [0.0, 1.0, 0.0] for t in texts]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def provider(client):
|
||||
value = ToyProvider()
|
||||
client.app.dependency_overrides[get_embedding_provider] = lambda: value
|
||||
return value
|
||||
|
||||
|
||||
def text_version(client, **kwargs):
|
||||
version = build(client, chunk_size=100, overlap=0, **kwargs)
|
||||
generation = client.get("/api/indexes/active").json()["generation"]
|
||||
assert publish(client, version, generation).status_code == 200
|
||||
return version
|
||||
|
||||
|
||||
def vector_build(client, version):
|
||||
response = client.post("/api/vector-indexes", json={"text_index_version_id": version["id"]})
|
||||
assert response.status_code == 201, response.text
|
||||
return response.json()
|
||||
|
||||
|
||||
def vector_publish(client, version, generation=0):
|
||||
return client.post(
|
||||
f"/api/vector-indexes/{version['id']}/publish", json={"expected_generation": generation}
|
||||
)
|
||||
|
||||
|
||||
def vector_search(client, **kwargs):
|
||||
return client.post("/api/retrieval/vector", json={"query": "alpha query", **kwargs})
|
||||
|
||||
|
||||
def test_exact_cosine_original_chunks_category_and_mixed_history(client, provider):
|
||||
first = add_item(client, content="alpha 占位正文", category="交通")
|
||||
second = add_item(client, content="beta 占位正文", category="其他")
|
||||
third = add_item(client, content="gamma 占位正文", category="其他")
|
||||
add_item(client, status="pending", content="alpha 待审核不应进入")
|
||||
text_ = text_version(client)
|
||||
version = vector_build(client, text_)
|
||||
assert (
|
||||
version["status"] == "ready" and version["dimension"] == 3 and version["chunk_count"] == 3
|
||||
)
|
||||
assert vector_search(client).status_code == 409
|
||||
assert vector_publish(client, version).json()["is_current"]
|
||||
result = vector_search(client).json()
|
||||
assert result["mode"] == "vector" and result["total_hits"] == 3
|
||||
assert [h["knowledge_id"] for h in result["results"]] == [
|
||||
first["id"],
|
||||
second["id"],
|
||||
third["id"],
|
||||
]
|
||||
assert [h["score"] for h in result["results"]] == [1.0, 0.0, 0.0]
|
||||
assert result["config"]["space"]["id"] == provider.spec.space_id
|
||||
assert result["text_index_version_id"] == text_["id"]
|
||||
assert result["vector_version_id"] == version["id"]
|
||||
assert result["corpus_fingerprint"] == text_["corpus_fingerprint"]
|
||||
assert result["elapsed_ms"] > 0
|
||||
assert result["results"][0]["snippet"] == first["content"]
|
||||
assert result["results"][0]["snippet_start"] == 0
|
||||
assert result["results"][0]["snippet_end"] == len(first["content"])
|
||||
assert result["results"][0]["source_url"] == "https://example.com/"
|
||||
filtered = vector_search(client, category="其他", top_k=1).json()
|
||||
assert filtered["corpus_size"] == 2 and filtered["results"][0]["knowledge_id"] == second["id"]
|
||||
empty = vector_search(client, category="不存在分类").json()
|
||||
assert empty["status"] == "no_evidence" and empty["results"] == []
|
||||
keyword = client.post("/api/retrieval", json={"query": "alpha"}).json()
|
||||
history = client.get("/api/retrieval/runs?page_size=100").json()
|
||||
assert {r["mode"] for r in history["items"]} == {"keyword", "vector"}
|
||||
assert client.get(f"/api/retrieval/runs/{result['run_id']}").json() == result
|
||||
assert client.get(f"/api/retrieval/runs/{keyword['run_id']}").json() == keyword
|
||||
client.cookies.clear()
|
||||
assert vector_search(client).status_code == 200
|
||||
assert client.get("/api/retrieval/runs").status_code == 401
|
||||
|
||||
|
||||
def test_multiple_chunks_keep_original_offsets_and_order(client, provider):
|
||||
item = add_item(client, content="alpha 占位正文。" * 45)
|
||||
version = vector_build(client, text_version(client))
|
||||
vector_publish(client, version)
|
||||
hits = vector_search(client, top_k=20).json()["results"]
|
||||
assert len(hits) > 1
|
||||
assert [h["chunk_ordinal"] for h in hits] == list(range(len(hits)))
|
||||
assert len({h["chunk_id"] for h in hits}) == len(hits)
|
||||
for h in hits:
|
||||
assert h["snippet"] == item["content"][h["snippet_start"] : h["snippet_end"]]
|
||||
|
||||
|
||||
def test_same_dimension_different_revision_is_a_separate_space(client, provider, migrated_database):
|
||||
add_item(client)
|
||||
text_ = text_version(client)
|
||||
first = vector_build(client, text_)
|
||||
vector_publish(client, first)
|
||||
old_space = provider.spec.space_id
|
||||
provider.spec = replace(provider.spec, revision="b" * 40)
|
||||
assert vector_search(client).status_code == 409
|
||||
second = vector_build(client, text_)
|
||||
assert second["space_id"] != old_space and second["dimension"] == first["dimension"]
|
||||
assert vector_publish(client, second, 1).status_code == 200
|
||||
assert vector_search(client).json()["config"]["space"]["id"] == provider.spec.space_id
|
||||
assert client.get("/api/vector-indexes/spaces").json()["total"] == 2
|
||||
with Session(migrated_database) as db:
|
||||
assert db.scalar(select(func.count()).select_from(EmbeddingSpace)) == 2
|
||||
assert db.scalar(select(func.count()).select_from(ChunkVector)) == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bad", ["dimension", "zero", "nan", "count", "overflow", "unavailable"])
|
||||
def test_invalid_or_failed_encode_never_persists_partial_vectors(
|
||||
client, provider, migrated_database, bad
|
||||
):
|
||||
add_item(client, content="alpha 占位")
|
||||
text_ = text_version(client)
|
||||
original = vector_build(client, text_)
|
||||
vector_publish(client, original)
|
||||
before = client.get("/api/vector-indexes/active").json()
|
||||
|
||||
def fail(texts, **kwargs):
|
||||
if bad == "overflow":
|
||||
raise EmbeddingError("token limit")
|
||||
if bad == "unavailable":
|
||||
raise OSError("private-path-must-not-appear")
|
||||
return {
|
||||
"dimension": [[1, 0]],
|
||||
"zero": [[0, 0, 0]],
|
||||
"nan": [[float("nan"), 0, 0]],
|
||||
"count": [],
|
||||
}[bad]
|
||||
|
||||
provider.encode = fail
|
||||
response = client.post("/api/vector-indexes", json={"text_index_version_id": text_["id"]})
|
||||
assert response.status_code == (503 if bad == "unavailable" else 422)
|
||||
assert "private-path" not in response.text
|
||||
assert client.get("/api/vector-indexes/active").json() == before
|
||||
with Session(migrated_database) as db:
|
||||
assert db.scalar(select(func.count()).select_from(VectorVersion)) == 1
|
||||
assert db.scalar(select(func.count()).select_from(ChunkVector)) == 1
|
||||
|
||||
|
||||
def test_build_and_query_encode_release_locks_but_recheck_catalog(
|
||||
client, provider, migrated_database
|
||||
):
|
||||
item = add_item(client, content="alpha 占位")
|
||||
text_ = text_version(client)
|
||||
original = vector_build(client, text_)
|
||||
vector_publish(client, original)
|
||||
|
||||
def mutate(query):
|
||||
with Session(migrated_database) as other:
|
||||
other.execute(text("SET LOCAL lock_timeout = '100ms'"))
|
||||
state = locked_state(other) # Must succeed during both document and query inference.
|
||||
state.catalog_revision += 1
|
||||
other.execute(
|
||||
text("UPDATE knowledge_items SET content=:value WHERE id=:id"),
|
||||
{"value": "修订占位", "id": item["id"]},
|
||||
)
|
||||
other.commit()
|
||||
|
||||
provider.callback = mutate
|
||||
response = client.post("/api/vector-indexes", json={"text_index_version_id": text_["id"]})
|
||||
assert response.status_code == 409
|
||||
assert not client.get("/api/vector-indexes/active").json()["is_current"]
|
||||
assert vector_search(client).status_code == 409
|
||||
# Restore an active text/vector snapshot, then race during query encoding too.
|
||||
provider.callback = None
|
||||
text_ = text_version(client)
|
||||
replacement = vector_build(client, text_)
|
||||
vector_publish(client, replacement, 1)
|
||||
provider.callback = mutate
|
||||
assert vector_search(client).status_code == 409
|
||||
with Session(migrated_database) as db:
|
||||
assert db.scalar(select(func.count()).select_from(VectorVersion)) == 2
|
||||
assert db.scalar(select(func.count()).select_from(RetrievalRun)) == 0
|
||||
|
||||
|
||||
def test_query_rejects_publication_changed_during_encode(client, provider, migrated_database):
|
||||
add_item(client)
|
||||
text_ = text_version(client)
|
||||
first, second = vector_build(client, text_), vector_build(client, text_)
|
||||
vector_publish(client, first)
|
||||
|
||||
def switch(query):
|
||||
if query:
|
||||
with Session(migrated_database) as other:
|
||||
vectors.publish_vectors(other, UUID(second["id"]), 1, second["created_by"])
|
||||
|
||||
provider.callback = switch
|
||||
assert vector_search(client).status_code == 409
|
||||
assert client.get("/api/vector-indexes/active").json()["active_version"]["id"] == second["id"]
|
||||
|
||||
|
||||
def test_text_switch_during_build_discards_new_vectors(client, provider, migrated_database):
|
||||
add_item(client)
|
||||
first = text_version(client)
|
||||
replacement = build(client)
|
||||
|
||||
def switch(query):
|
||||
with Session(migrated_database) as other:
|
||||
publish_index(other, UUID(replacement["id"]), 1, replacement["created_by"])
|
||||
|
||||
provider.callback = switch
|
||||
assert (
|
||||
client.post("/api/vector-indexes", json={"text_index_version_id": first["id"]}).status_code
|
||||
== 409
|
||||
)
|
||||
assert client.get("/api/vector-indexes").json()["total"] == 0
|
||||
|
||||
|
||||
def test_out_of_band_evidence_change_fails_closed(client, provider, migrated_database):
|
||||
item = add_item(client)
|
||||
text_ = text_version(client)
|
||||
version = vector_build(client, text_)
|
||||
vector_publish(client, version)
|
||||
with migrated_database.begin() as c:
|
||||
c.execute(
|
||||
text("UPDATE knowledge_items SET content='SQL 更新占位' WHERE id=:id"),
|
||||
{"id": item["id"]},
|
||||
)
|
||||
assert not client.get("/api/vector-indexes/active").json()["is_current"]
|
||||
assert vector_search(client).status_code == 409
|
||||
assert vector_publish(client, version, 1).status_code == 409
|
||||
|
||||
|
||||
def test_optimistic_publication_audit_and_rollback(client, provider, migrated_database):
|
||||
add_item(client)
|
||||
text_ = text_version(client)
|
||||
first, second = vector_build(client, text_), vector_build(client, text_)
|
||||
assert vector_publish(client, first).json()["generation"] == 1
|
||||
assert vector_publish(client, second).status_code == 409
|
||||
assert vector_publish(client, first, 1).json()["generation"] == 1
|
||||
with migrated_database.begin() as c:
|
||||
c.execute(
|
||||
text(
|
||||
"ALTER TABLE vector_publications ADD CONSTRAINT test_reject_publication CHECK (false) NOT VALID"
|
||||
)
|
||||
)
|
||||
assert vector_publish(client, second, 1).status_code == 409
|
||||
assert client.get("/api/vector-indexes/active").json()["active_version"]["id"] == first["id"]
|
||||
assert client.get(f"/api/vector-indexes/{first['id']}").json()["status"] == "published"
|
||||
assert client.get(f"/api/vector-indexes/{second['id']}").json()["status"] == "ready"
|
||||
assert client.get("/api/vector-indexes/publications").json()["total"] == 1
|
||||
with migrated_database.begin() as c:
|
||||
c.execute(text("ALTER TABLE vector_publications DROP CONSTRAINT test_reject_publication"))
|
||||
assert vector_publish(client, second, 1).json()["generation"] == 2
|
||||
assert client.get(f"/api/vector-indexes/{first['id']}").json()["status"] == "superseded"
|
||||
|
||||
|
||||
def test_vector_insert_failure_rolls_back_entire_build(client, provider, migrated_database):
|
||||
add_item(client)
|
||||
text_ = text_version(client)
|
||||
with migrated_database.begin() as c:
|
||||
c.execute(
|
||||
text(
|
||||
"ALTER TABLE chunk_vectors ADD CONSTRAINT test_reject_vector CHECK (false) NOT VALID"
|
||||
)
|
||||
)
|
||||
assert (
|
||||
client.post("/api/vector-indexes", json={"text_index_version_id": text_["id"]}).status_code
|
||||
== 409
|
||||
)
|
||||
with Session(migrated_database) as db:
|
||||
assert db.scalar(select(func.count()).select_from(VectorVersion)) == 0
|
||||
assert db.scalar(select(func.count()).select_from(ChunkVector)) == 0
|
||||
assert db.scalar(select(func.count()).select_from(EmbeddingSpace)) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"column,value",
|
||||
[
|
||||
("dimension", 2),
|
||||
("space_id", "0" * 64),
|
||||
("text_index_version_id", str(uuid4())),
|
||||
("embedding", [1, 0]),
|
||||
("embedding", [2, 0, 0]),
|
||||
("embedding", [float("nan"), 0, 0]),
|
||||
],
|
||||
)
|
||||
def test_postgresql_rejects_cross_scope_dimensions_and_nonunit_values(
|
||||
client, provider, migrated_database, column, value
|
||||
):
|
||||
add_item(client)
|
||||
version = vector_build(client, text_version(client))
|
||||
with Session(migrated_database) as db:
|
||||
with pytest.raises(DBAPIError):
|
||||
db.execute(
|
||||
update(ChunkVector)
|
||||
.where(ChunkVector.version_id == version["id"])
|
||||
.values(**{column: value})
|
||||
)
|
||||
db.commit()
|
||||
db.rollback()
|
||||
row = db.scalar(select(ChunkVector))
|
||||
assert row.space_id == provider.spec.space_id and row.dimension == 3
|
||||
|
||||
|
||||
def test_same_width_cross_space_and_cross_text_chunks_are_rejected_by_database(
|
||||
client, provider, migrated_database
|
||||
):
|
||||
add_item(client)
|
||||
original_text = text_version(client)
|
||||
first = vector_build(client, original_text)
|
||||
provider.spec = replace(provider.spec, revision="c" * 40)
|
||||
second = vector_build(client, original_text)
|
||||
newer_text = text_version(client)
|
||||
with Session(migrated_database) as db:
|
||||
other_chunk = db.scalar(select(Chunk.id).where(Chunk.index_version_id == newer_text["id"]))
|
||||
for fields in [{"space_id": second["space_id"]}, {"chunk_id": other_chunk}]:
|
||||
with pytest.raises(DBAPIError):
|
||||
db.execute(
|
||||
update(ChunkVector)
|
||||
.where(ChunkVector.version_id == first["id"])
|
||||
.values(**fields)
|
||||
)
|
||||
db.commit()
|
||||
db.rollback()
|
||||
|
||||
|
||||
def test_incomplete_vector_set_cannot_be_published_or_queried(client, provider, migrated_database):
|
||||
add_item(client)
|
||||
version = vector_build(client, text_version(client))
|
||||
vector_publish(client, version)
|
||||
with migrated_database.begin() as c:
|
||||
c.execute(text("DELETE FROM chunk_vectors"))
|
||||
assert vector_publish(client, version, 1).status_code == 409
|
||||
assert vector_search(client).status_code == 409
|
||||
|
||||
|
||||
def test_only_current_text_and_bounded_complete_snapshots_can_build(
|
||||
client, provider, migrated_database, monkeypatch
|
||||
):
|
||||
assert vector_search(client).status_code == 409
|
||||
add_item(client)
|
||||
unpublished = build(client)
|
||||
assert (
|
||||
client.post(
|
||||
"/api/vector-indexes", json={"text_index_version_id": unpublished["id"]}
|
||||
).status_code
|
||||
== 409
|
||||
)
|
||||
text_ = text_version(client)
|
||||
assert (
|
||||
client.post(
|
||||
"/api/vector-indexes", json={"text_index_version_id": unpublished["id"]}
|
||||
).status_code
|
||||
== 409
|
||||
)
|
||||
monkeypatch.setattr(vectors, "MAX_VECTOR_CHUNKS", 0)
|
||||
assert (
|
||||
client.post("/api/vector-indexes", json={"text_index_version_id": text_["id"]}).status_code
|
||||
== 503
|
||||
)
|
||||
monkeypatch.undo()
|
||||
with migrated_database.begin() as c:
|
||||
c.execute(text("DELETE FROM chunks"))
|
||||
assert (
|
||||
client.post("/api/vector-indexes", json={"text_index_version_id": text_["id"]}).status_code
|
||||
== 409
|
||||
)
|
||||
|
||||
|
||||
def test_empty_reviewed_corpus_can_be_published_without_fabricated_results(client, provider):
|
||||
version = vector_build(client, text_version(client))
|
||||
assert version["chunk_count"] == 0
|
||||
assert vector_publish(client, version).json()["is_current"]
|
||||
result = vector_search(client).json()
|
||||
assert (
|
||||
result["status"] == "no_evidence" and result["results"] == [] and result["corpus_size"] == 0
|
||||
)
|
||||
|
||||
|
||||
def test_metadata_and_build_require_admin_and_csrf(client, provider):
|
||||
add_item(client)
|
||||
version = vector_build(client, text_version(client))
|
||||
assert (
|
||||
client.post(
|
||||
"/api/vector-indexes",
|
||||
json={"text_index_version_id": version["text_index_version_id"]},
|
||||
headers={"X-CSRF-Token": ""},
|
||||
).status_code
|
||||
== 403
|
||||
)
|
||||
client.cookies.clear()
|
||||
for method, path in [
|
||||
("GET", "/api/vector-indexes"),
|
||||
("GET", "/api/vector-indexes/active"),
|
||||
("GET", "/api/vector-indexes/spaces"),
|
||||
("GET", "/api/vector-indexes/publications"),
|
||||
("GET", f"/api/vector-indexes/{version['id']}"),
|
||||
("POST", "/api/vector-indexes"),
|
||||
("POST", f"/api/vector-indexes/{version['id']}/publish"),
|
||||
]:
|
||||
assert client.request(method, path, json={}).status_code == 401
|
||||
assert (
|
||||
client.post(
|
||||
"/api/auth/register", json={"username": "vector_user", "password": "test-password-123"}
|
||||
).status_code
|
||||
== 201
|
||||
)
|
||||
auth = client.post(
|
||||
"/api/auth/login", json={"username": "vector_user", "password": "test-password-123"}
|
||||
).json()
|
||||
client.headers["X-CSRF-Token"] = auth["csrf_token"]
|
||||
assert client.get("/api/vector-indexes").status_code == 403
|
||||
assert client.post("/api/vector-indexes", json={}).status_code == 403
|
||||
|
||||
|
||||
def test_vector_publication_serializes_with_catalog_and_other_publishers(
|
||||
client, provider, migrated_database
|
||||
):
|
||||
add_item(client)
|
||||
text_ = text_version(client)
|
||||
first, second = vector_build(client, text_), vector_build(client, text_)
|
||||
with Session(migrated_database) as owner, Session(migrated_database) as other:
|
||||
locked_state(owner)
|
||||
other.execute(text("SET LOCAL lock_timeout = '100ms'"))
|
||||
with pytest.raises(OperationalError) as blocked:
|
||||
vectors.publish_vectors(other, UUID(first["id"]), 0, first["created_by"])
|
||||
assert blocked.value.orig.sqlstate == "55P03"
|
||||
other.rollback()
|
||||
vectors.publish_vectors(owner, UUID(first["id"]), 0, first["created_by"])
|
||||
with pytest.raises(HTTPException) as stale:
|
||||
vectors.publish_vectors(other, UUID(second["id"]), 0, second["created_by"])
|
||||
assert stale.value.status_code == 409
|
||||
other.rollback()
|
||||
with Session(migrated_database) as db:
|
||||
assert db.scalar(select(func.count()).select_from(VectorPublication)) == 1
|
||||
|
||||
|
||||
def test_lazy_runtime_is_offline_and_does_not_wait_when_busy(monkeypatch):
|
||||
from app.services import embedding_runtime
|
||||
|
||||
calls = []
|
||||
|
||||
def load(spec, **kwargs):
|
||||
calls.append(kwargs)
|
||||
return ToyProvider()
|
||||
|
||||
monkeypatch.setattr(embedding_runtime, "LocalEmbeddingProvider", load)
|
||||
runtime = EmbeddingRuntime(ToyProvider.spec, None, 8, 2)
|
||||
assert calls == []
|
||||
assert runtime.encode(["alpha"]) == [[1, 0, 0]]
|
||||
assert calls[0]["offline"] is True
|
||||
runtime.encode(["alpha"], query=True)
|
||||
assert len(calls) == 1
|
||||
with runtime._lock:
|
||||
with pytest.raises(HTTPException) as busy:
|
||||
runtime.encode(["alpha"])
|
||||
assert busy.value.status_code == 503
|
||||
|
||||
|
||||
def test_disabled_runtime_is_explicit_and_keyword_baseline_remains_available(client):
|
||||
assert vector_search(client).status_code == 503
|
||||
assert client.post("/api/retrieval", json={"query": "那拉提"}).status_code == 200
|
||||
|
||||
|
||||
def test_different_width_spaces_coexist_and_queries_never_compare_across_widths(client, provider):
|
||||
add_item(client)
|
||||
text_ = text_version(client)
|
||||
first = vector_build(client, text_)
|
||||
vector_publish(client, first)
|
||||
provider.spec = replace(provider.spec, dimension=2)
|
||||
provider.encode = lambda texts, **kwargs: [[1, 0] for _ in texts]
|
||||
second = vector_build(client, text_)
|
||||
assert vector_publish(client, second, 1).status_code == 200
|
||||
result = vector_search(client)
|
||||
assert result.status_code == 200, result.text
|
||||
assert result.json()["total_hits"] == 1 and result.json()["results"][0]["score"] == 1
|
||||
assert client.get("/api/vector-indexes/spaces").json()["total"] == 2
|
||||
|
||||
|
||||
def test_new_text_publication_requires_corresponding_vectors_and_keeps_history(client, provider):
|
||||
add_item(client)
|
||||
old_text = text_version(client)
|
||||
old = vector_build(client, old_text)
|
||||
vector_publish(client, old)
|
||||
history = vector_search(client).json()
|
||||
new_text = text_version(client)
|
||||
active = client.get("/api/vector-indexes/active").json()
|
||||
assert not active["is_current"] and active["active_version"]["id"] == old["id"]
|
||||
assert vector_search(client).status_code == 409
|
||||
assert vector_publish(client, old, 1).status_code == 409
|
||||
replacement = vector_build(client, new_text)
|
||||
assert vector_publish(client, replacement, 1).json()["is_current"]
|
||||
assert client.get(f"/api/retrieval/runs/{history['run_id']}").json() == history
|
||||
assert client.get("/api/vector-indexes/publications").json()["total"] == 2
|
||||
|
||||
|
||||
def test_snapshot_text_corruption_cannot_be_encoded_published_or_served(
|
||||
client, provider, migrated_database
|
||||
):
|
||||
add_item(client)
|
||||
text_ = text_version(client)
|
||||
version = vector_build(client, text_)
|
||||
vector_publish(client, version)
|
||||
with migrated_database.begin() as c:
|
||||
c.execute(text("UPDATE chunks SET text='篡改占位'"))
|
||||
assert not client.get("/api/vector-indexes/active").json()["is_current"]
|
||||
assert (
|
||||
client.post("/api/vector-indexes", json={"text_index_version_id": text_["id"]}).status_code
|
||||
== 409
|
||||
)
|
||||
assert vector_publish(client, version, 1).status_code == 409
|
||||
assert vector_search(client).status_code == 409
|
||||
|
||||
|
||||
def test_request_shape_pagination_and_missing_vector_version(client, provider):
|
||||
assert (
|
||||
client.post(
|
||||
"/api/vector-indexes", json={"text_index_version_id": str(uuid4()), "embedding": [1, 0]}
|
||||
).status_code
|
||||
== 422
|
||||
)
|
||||
assert vector_search(client, top_k=21).status_code == 422
|
||||
assert vector_search(client, mode="keyword").status_code == 422
|
||||
assert vector_search(client, query=" ").status_code == 422
|
||||
assert client.get("/api/vector-indexes?page=0").status_code == 422
|
||||
assert client.get(f"/api/vector-indexes/{uuid4()}").status_code == 404
|
||||
assert client.get("/api/vector-indexes/spaces").json()["total"] == 0
|
||||
|
||||
|
||||
def test_blank_optional_cache_path_is_not_the_working_directory():
|
||||
from app.core.config import Settings
|
||||
|
||||
assert Settings(embedding_cache_dir="", _env_file=None).embedding_cache_dir is None
|
||||
Reference in New Issue
Block a user