Compare commits
1 Commits
codex/requ
...
sim117
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
644dd8b281 |
@@ -84,7 +84,6 @@ ignore = [
|
||||
"SIM105", # suppressible-exception
|
||||
"SIM108", # if-else-block-instead-of-if-exp
|
||||
"SIM113", # enumerate-for-loop
|
||||
"SIM117", # multiple-with-statements
|
||||
"SIM210", # if-expr-with-true-false
|
||||
"TID252", # allow relative imports from parent modules
|
||||
]
|
||||
|
||||
@@ -1531,16 +1531,13 @@ class ToolMCPAuthApi(Resource):
|
||||
timeout=provider_entity.timeout,
|
||||
sse_read_timeout=provider_entity.sse_read_timeout,
|
||||
):
|
||||
# Update credentials in new transaction
|
||||
with sessionmaker(db.engine).begin() as session:
|
||||
service = MCPToolManageService(session=session)
|
||||
service.update_provider_credentials(
|
||||
provider_id=provider_id,
|
||||
tenant_id=tenant_id,
|
||||
credentials=provider_entity.credentials,
|
||||
authed=True,
|
||||
)
|
||||
return MCPAuthResponse(result="success").model_dump(mode="json")
|
||||
MCPToolManageService.update_provider_credentials_in_new_transaction(
|
||||
provider_id=provider_id,
|
||||
tenant_id=tenant_id,
|
||||
credentials=provider_entity.credentials,
|
||||
authed=True,
|
||||
)
|
||||
return MCPAuthResponse(result="success").model_dump(mode="json")
|
||||
except MCPAuthError as e:
|
||||
try:
|
||||
# Pass the extracted OAuth metadata hints to auth()
|
||||
|
||||
@@ -120,45 +120,42 @@ class MessageCycleManager:
|
||||
query: str,
|
||||
message_id: str | None = None,
|
||||
):
|
||||
with flask_app.app_context():
|
||||
with session_factory.create_session() as session:
|
||||
# get conversation and message
|
||||
stmt = select(Conversation).where(Conversation.id == conversation_id)
|
||||
conversation = session.scalar(stmt)
|
||||
with flask_app.app_context(), session_factory.create_session() as session:
|
||||
# get conversation and message
|
||||
stmt = select(Conversation).where(Conversation.id == conversation_id)
|
||||
conversation = session.scalar(stmt)
|
||||
|
||||
if not conversation:
|
||||
if not conversation:
|
||||
return
|
||||
|
||||
if conversation.mode != AppMode.COMPLETION:
|
||||
app_model = session.get(App, conversation.app_id)
|
||||
if not app_model:
|
||||
return
|
||||
|
||||
if conversation.mode != AppMode.COMPLETION:
|
||||
app_model = session.get(App, conversation.app_id)
|
||||
if not app_model:
|
||||
return
|
||||
# generate conversation name
|
||||
query_hash = hashlib.md5(query.encode()).hexdigest()[:16]
|
||||
cache_key = f"conv_name:{conversation_id}:{query_hash}"
|
||||
|
||||
# generate conversation name
|
||||
query_hash = hashlib.md5(query.encode()).hexdigest()[:16]
|
||||
cache_key = f"conv_name:{conversation_id}:{query_hash}"
|
||||
|
||||
cached_name = redis_client.get(cache_key)
|
||||
if cached_name:
|
||||
name = cached_name.decode("utf-8")
|
||||
else:
|
||||
try:
|
||||
name = LLMGenerator.generate_conversation_name(
|
||||
app_model.tenant_id,
|
||||
query,
|
||||
conversation_id,
|
||||
conversation.app_id,
|
||||
message_id=message_id,
|
||||
)
|
||||
redis_client.setex(cache_key, 3600, name)
|
||||
except Exception:
|
||||
if dify_config.DEBUG:
|
||||
logger.exception(
|
||||
"generate conversation name failed, conversation_id: %s", conversation_id
|
||||
)
|
||||
name = query[:47] + "..." if len(query) > 50 else query
|
||||
conversation.name = name
|
||||
session.commit()
|
||||
cached_name = redis_client.get(cache_key)
|
||||
if cached_name:
|
||||
name = cached_name.decode("utf-8")
|
||||
else:
|
||||
try:
|
||||
name = LLMGenerator.generate_conversation_name(
|
||||
app_model.tenant_id,
|
||||
query,
|
||||
conversation_id,
|
||||
conversation.app_id,
|
||||
message_id=message_id,
|
||||
)
|
||||
redis_client.setex(cache_key, 3600, name)
|
||||
except Exception:
|
||||
if dify_config.DEBUG:
|
||||
logger.exception("generate conversation name failed, conversation_id: %s", conversation_id)
|
||||
name = query[:47] + "..." if len(query) > 50 else query
|
||||
conversation.name = name
|
||||
session.commit()
|
||||
|
||||
def handle_annotation_reply(self, event: QueueAnnotationReplyEvent, session: Session) -> MessageAnnotation | None:
|
||||
"""
|
||||
|
||||
@@ -716,73 +716,19 @@ class IndexingRunner:
|
||||
|
||||
@staticmethod
|
||||
def _process_keyword_index(flask_app: Flask, dataset_id: str, document_id: str, documents: list[Document]):
|
||||
with flask_app.app_context():
|
||||
with session_factory.create_session() as session:
|
||||
dataset = session.get(Dataset, dataset_id)
|
||||
if not dataset:
|
||||
raise ValueError("no dataset found")
|
||||
keyword = Keyword(dataset)
|
||||
keyword.create(documents, session)
|
||||
if dataset.indexing_technique != IndexTechniqueType.HIGH_QUALITY:
|
||||
document_ids = [document.metadata["doc_id"] for document in documents]
|
||||
session.execute(
|
||||
update(DocumentSegment)
|
||||
.where(
|
||||
DocumentSegment.document_id == document_id,
|
||||
DocumentSegment.dataset_id == dataset_id,
|
||||
DocumentSegment.index_node_id.in_(document_ids),
|
||||
DocumentSegment.status == SegmentStatus.INDEXING,
|
||||
)
|
||||
.values(
|
||||
status=SegmentStatus.COMPLETED,
|
||||
enabled=True,
|
||||
completed_at=naive_utc_now(),
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
|
||||
def _process_chunk(
|
||||
self,
|
||||
flask_app: Flask,
|
||||
index_type: str,
|
||||
chunk_documents: list[Document],
|
||||
dataset_id: str,
|
||||
dataset_document_id: str,
|
||||
) -> None:
|
||||
with flask_app.app_context():
|
||||
with session_factory.create_session() as session:
|
||||
dataset = session.get(Dataset, dataset_id)
|
||||
if not dataset:
|
||||
raise ValueError("no dataset found")
|
||||
|
||||
dataset_document = session.get(DatasetDocument, dataset_document_id)
|
||||
if not dataset_document:
|
||||
raise ValueError("no document found")
|
||||
|
||||
# check document is paused
|
||||
self._check_document_paused_status(dataset_document.id)
|
||||
|
||||
multimodal_documents = []
|
||||
for document in chunk_documents:
|
||||
if document.attachments and dataset.is_multimodal:
|
||||
multimodal_documents.extend(document.attachments)
|
||||
|
||||
# load index
|
||||
index_processor = IndexProcessorFactory(index_type).init_index_processor()
|
||||
index_processor.load(
|
||||
dataset,
|
||||
chunk_documents,
|
||||
multimodal_documents=multimodal_documents,
|
||||
with_keywords=False,
|
||||
session=session,
|
||||
)
|
||||
|
||||
document_ids = [document.metadata["doc_id"] for document in chunk_documents]
|
||||
with flask_app.app_context(), session_factory.create_session() as session:
|
||||
dataset = session.get(Dataset, dataset_id)
|
||||
if not dataset:
|
||||
raise ValueError("no dataset found")
|
||||
keyword = Keyword(dataset)
|
||||
keyword.create(documents, session)
|
||||
if dataset.indexing_technique != IndexTechniqueType.HIGH_QUALITY:
|
||||
document_ids = [document.metadata["doc_id"] for document in documents]
|
||||
session.execute(
|
||||
update(DocumentSegment)
|
||||
.where(
|
||||
DocumentSegment.document_id == dataset_document.id,
|
||||
DocumentSegment.dataset_id == dataset.id,
|
||||
DocumentSegment.document_id == document_id,
|
||||
DocumentSegment.dataset_id == dataset_id,
|
||||
DocumentSegment.index_node_id.in_(document_ids),
|
||||
DocumentSegment.status == SegmentStatus.INDEXING,
|
||||
)
|
||||
@@ -792,8 +738,60 @@ class IndexingRunner:
|
||||
completed_at=naive_utc_now(),
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
|
||||
session.commit()
|
||||
def _process_chunk(
|
||||
self,
|
||||
flask_app: Flask,
|
||||
index_type: str,
|
||||
chunk_documents: list[Document],
|
||||
dataset_id: str,
|
||||
dataset_document_id: str,
|
||||
) -> None:
|
||||
with flask_app.app_context(), session_factory.create_session() as session:
|
||||
dataset = session.get(Dataset, dataset_id)
|
||||
if not dataset:
|
||||
raise ValueError("no dataset found")
|
||||
|
||||
dataset_document = session.get(DatasetDocument, dataset_document_id)
|
||||
if not dataset_document:
|
||||
raise ValueError("no document found")
|
||||
|
||||
# check document is paused
|
||||
self._check_document_paused_status(dataset_document.id)
|
||||
|
||||
multimodal_documents = []
|
||||
for document in chunk_documents:
|
||||
if document.attachments and dataset.is_multimodal:
|
||||
multimodal_documents.extend(document.attachments)
|
||||
|
||||
# load index
|
||||
index_processor = IndexProcessorFactory(index_type).init_index_processor()
|
||||
index_processor.load(
|
||||
dataset,
|
||||
chunk_documents,
|
||||
multimodal_documents=multimodal_documents,
|
||||
with_keywords=False,
|
||||
session=session,
|
||||
)
|
||||
|
||||
document_ids = [document.metadata["doc_id"] for document in chunk_documents]
|
||||
session.execute(
|
||||
update(DocumentSegment)
|
||||
.where(
|
||||
DocumentSegment.document_id == dataset_document.id,
|
||||
DocumentSegment.dataset_id == dataset.id,
|
||||
DocumentSegment.index_node_id.in_(document_ids),
|
||||
DocumentSegment.status == SegmentStatus.INDEXING,
|
||||
)
|
||||
.values(
|
||||
status=SegmentStatus.COMPLETED,
|
||||
enabled=True,
|
||||
completed_at=naive_utc_now(),
|
||||
)
|
||||
)
|
||||
|
||||
session.commit()
|
||||
|
||||
@staticmethod
|
||||
def _check_document_paused_status(document_id: str):
|
||||
|
||||
@@ -283,15 +283,17 @@ def sse_client(
|
||||
|
||||
executor = ThreadPoolExecutor()
|
||||
try:
|
||||
with create_ssrf_proxy_mcp_http_client(headers=transport.headers) as client:
|
||||
with ssrf_proxy_sse_connect(
|
||||
with (
|
||||
create_ssrf_proxy_mcp_http_client(headers=transport.headers) as client,
|
||||
ssrf_proxy_sse_connect(
|
||||
url, timeout=httpx.Timeout(timeout, read=sse_read_timeout), client=client
|
||||
) as event_source:
|
||||
event_source.response.raise_for_status()
|
||||
) as event_source,
|
||||
):
|
||||
event_source.response.raise_for_status()
|
||||
|
||||
read_queue, write_queue = transport.connect(executor, client, event_source)
|
||||
read_queue, write_queue = transport.connect(executor, client, event_source)
|
||||
|
||||
yield read_queue, write_queue
|
||||
yield read_queue, write_queue
|
||||
|
||||
except httpx.HTTPStatusError as exc:
|
||||
if exc.response.status_code == 401:
|
||||
|
||||
@@ -236,17 +236,16 @@ class IndexProcessor:
|
||||
def generate_summary_for_chunk(preview_item: PreviewItem) -> None:
|
||||
"""Generate summary for a single chunk."""
|
||||
if flask_app:
|
||||
with flask_app.app_context():
|
||||
with session_factory.create_session() as worker_session:
|
||||
summary, _ = ParagraphIndexProcessor.generate_summary(
|
||||
tenant_id=tenant_id,
|
||||
text=preview_item.content if preview_item.content is not None else "",
|
||||
summary_index_setting=summary_index_setting,
|
||||
document_language=doc_language,
|
||||
session=worker_session,
|
||||
)
|
||||
if summary:
|
||||
preview_item.summary = summary
|
||||
with flask_app.app_context(), session_factory.create_session() as worker_session:
|
||||
summary, _ = ParagraphIndexProcessor.generate_summary(
|
||||
tenant_id=tenant_id,
|
||||
text=preview_item.content if preview_item.content is not None else "",
|
||||
summary_index_setting=summary_index_setting,
|
||||
document_language=doc_language,
|
||||
session=worker_session,
|
||||
)
|
||||
if summary:
|
||||
preview_item.summary = summary
|
||||
|
||||
# Generate summaries concurrently using ThreadPoolExecutor
|
||||
# Set a reasonable timeout to prevent hanging (60 seconds per chunk, max 5 minutes total)
|
||||
|
||||
@@ -178,12 +178,11 @@ class AliyunLogStorePG:
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
with self._get_connection() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
placeholders = ", ".join(["%s"] * len(fields))
|
||||
values_literal = cursor.mogrify(f"({placeholders})", values).decode("utf-8")
|
||||
insert_sql = f'INSERT INTO "{logstore}" ({field_list}) VALUES {values_literal}'
|
||||
cursor.execute(insert_sql)
|
||||
with self._get_connection() as conn, conn.cursor() as cursor:
|
||||
placeholders = ", ".join(["%s"] * len(fields))
|
||||
values_literal = cursor.mogrify(f"({placeholders})", values).decode("utf-8")
|
||||
insert_sql = f'INSERT INTO "{logstore}" ({field_list}) VALUES {values_literal}'
|
||||
cursor.execute(insert_sql)
|
||||
return
|
||||
|
||||
except psycopg2.Error as e:
|
||||
@@ -220,26 +219,25 @@ class AliyunLogStorePG:
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
with self._get_connection() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute(sql)
|
||||
columns = [desc[0] for desc in cursor.description]
|
||||
with self._get_connection() as conn, conn.cursor() as cursor:
|
||||
cursor.execute(sql)
|
||||
columns = [desc[0] for desc in cursor.description]
|
||||
|
||||
result = []
|
||||
for row in cursor.fetchall():
|
||||
row_dict = {}
|
||||
for col, val in zip(columns, row):
|
||||
row_dict[col] = "" if val is None else str(val)
|
||||
result.append(row_dict)
|
||||
result = []
|
||||
for row in cursor.fetchall():
|
||||
row_dict = {}
|
||||
for col, val in zip(columns, row):
|
||||
row_dict[col] = "" if val is None else str(val)
|
||||
result.append(row_dict)
|
||||
|
||||
if log_enabled:
|
||||
logger.info(
|
||||
"[LogStore-PG] EXECUTE_SQL RESULT | logstore=%s | returned_count=%d",
|
||||
logstore,
|
||||
len(result),
|
||||
)
|
||||
if log_enabled:
|
||||
logger.info(
|
||||
"[LogStore-PG] EXECUTE_SQL RESULT | logstore=%s | returned_count=%d",
|
||||
logstore,
|
||||
len(result),
|
||||
)
|
||||
|
||||
return result
|
||||
return result
|
||||
|
||||
except psycopg2.Error as e:
|
||||
if not self._is_retriable_error(e):
|
||||
|
||||
@@ -719,9 +719,11 @@ def test_setup_tracer_phoenix(mock_provider, mock_exporter):
|
||||
|
||||
def test_setup_tracer_exception():
|
||||
config = ArizeConfig(endpoint="http://a.com", project="p")
|
||||
with patch("dify_trace_arize_phoenix.arize_phoenix_trace.urlparse", side_effect=Exception("boom")):
|
||||
with pytest.raises(Exception, match="boom"):
|
||||
setup_tracer(config)
|
||||
with (
|
||||
patch("dify_trace_arize_phoenix.arize_phoenix_trace.urlparse", side_effect=Exception("boom")),
|
||||
pytest.raises(Exception, match="boom"),
|
||||
):
|
||||
setup_tracer(config)
|
||||
|
||||
|
||||
# --- ArizePhoenixDataTrace Class Tests ---
|
||||
@@ -785,9 +787,8 @@ def test_trace_dispatch(trace_instance):
|
||||
|
||||
|
||||
def test_trace_exception(trace_instance):
|
||||
with patch.object(trace_instance, "workflow_trace", side_effect=RuntimeError("fail")):
|
||||
with pytest.raises(RuntimeError):
|
||||
trace_instance.trace(_make_workflow_info())
|
||||
with patch.object(trace_instance, "workflow_trace", side_effect=RuntimeError("fail")), pytest.raises(RuntimeError):
|
||||
trace_instance.trace(_make_workflow_info())
|
||||
|
||||
|
||||
@patch("dify_trace_arize_phoenix.arize_phoenix_trace.DifyCoreRepositoryFactory")
|
||||
|
||||
@@ -321,9 +321,11 @@ class TestTraceDispatcher:
|
||||
mock_gn.assert_called_once()
|
||||
|
||||
def test_reraises_exception(self, trace_instance, mock_tracing, mock_db):
|
||||
with patch.object(trace_instance, "workflow_trace", side_effect=RuntimeError("boom")):
|
||||
with pytest.raises(RuntimeError, match="boom"):
|
||||
trace_instance.trace(_make_workflow_trace_info())
|
||||
with (
|
||||
patch.object(trace_instance, "workflow_trace", side_effect=RuntimeError("boom")),
|
||||
pytest.raises(RuntimeError, match="boom"),
|
||||
):
|
||||
trace_instance.trace(_make_workflow_trace_info())
|
||||
|
||||
|
||||
# ── workflow_trace ───────────────────────────────────────────────────────────
|
||||
|
||||
@@ -449,10 +449,9 @@ class ClickzettaVector(BaseVector):
|
||||
def _table_exists(self) -> bool:
|
||||
"""Check if the table exists."""
|
||||
try:
|
||||
with self.get_connection_context() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(f"DESC {self._config.schema_name}.{self._table_name}")
|
||||
return True
|
||||
with self.get_connection_context() as connection, connection.cursor() as cursor:
|
||||
cursor.execute(f"DESC {self._config.schema_name}.{self._table_name}")
|
||||
return True
|
||||
except Exception as e:
|
||||
error_message = str(e).lower()
|
||||
# Handle ClickZetta specific "table or view not found" errors
|
||||
@@ -502,17 +501,16 @@ class ClickzettaVector(BaseVector):
|
||||
) COMMENT 'Dify RAG knowledge base vector storage table for document embeddings and content'
|
||||
"""
|
||||
|
||||
with self.get_connection_context() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(create_table_sql)
|
||||
logger.info("Created table %s.%s", self._config.schema_name, self._table_name)
|
||||
with self.get_connection_context() as connection, connection.cursor() as cursor:
|
||||
cursor.execute(create_table_sql)
|
||||
logger.info("Created table %s.%s", self._config.schema_name, self._table_name)
|
||||
|
||||
# Create vector index
|
||||
self._create_vector_index(cursor)
|
||||
# Create vector index
|
||||
self._create_vector_index(cursor)
|
||||
|
||||
# Create inverted index for full-text search if enabled
|
||||
if self._config.enable_inverted_index:
|
||||
self._create_inverted_index(cursor)
|
||||
# Create inverted index for full-text search if enabled
|
||||
if self._config.enable_inverted_index:
|
||||
self._create_inverted_index(cursor)
|
||||
|
||||
def _create_vector_index(self, cursor):
|
||||
"""Create HNSW vector index for similarity search."""
|
||||
@@ -688,37 +686,36 @@ class ClickzettaVector(BaseVector):
|
||||
f"VALUES (?, ?, CAST(? AS JSON), CAST(? AS VECTOR({vector_dimension})))"
|
||||
)
|
||||
|
||||
with self.get_connection_context() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
with self.get_connection_context() as connection, connection.cursor() as cursor:
|
||||
try:
|
||||
# Set session-level hints for batch insert operations
|
||||
# Note: executemany doesn't support hints parameter, so we set them as session variables
|
||||
# Temporarily suppress ClickZetta client logging to reduce noise
|
||||
clickzetta_logger = logging.getLogger("clickzetta")
|
||||
original_level = clickzetta_logger.level
|
||||
clickzetta_logger.setLevel(logging.WARNING)
|
||||
|
||||
try:
|
||||
# Set session-level hints for batch insert operations
|
||||
# Note: executemany doesn't support hints parameter, so we set them as session variables
|
||||
# Temporarily suppress ClickZetta client logging to reduce noise
|
||||
clickzetta_logger = logging.getLogger("clickzetta")
|
||||
original_level = clickzetta_logger.level
|
||||
clickzetta_logger.setLevel(logging.WARNING)
|
||||
cursor.execute("SET cz.sql.job.fast.mode = true")
|
||||
cursor.execute("SET cz.sql.compaction.after.commit = true")
|
||||
cursor.execute("SET cz.storage.always.prefetch.internal = true")
|
||||
finally:
|
||||
# Restore original logging level
|
||||
clickzetta_logger.setLevel(original_level)
|
||||
|
||||
try:
|
||||
cursor.execute("SET cz.sql.job.fast.mode = true")
|
||||
cursor.execute("SET cz.sql.compaction.after.commit = true")
|
||||
cursor.execute("SET cz.storage.always.prefetch.internal = true")
|
||||
finally:
|
||||
# Restore original logging level
|
||||
clickzetta_logger.setLevel(original_level)
|
||||
|
||||
cursor.executemany(insert_sql, data_rows)
|
||||
logger.info(
|
||||
"Inserted batch %d/%d (%d valid docs using parameterized query with VECTOR(%d) cast)",
|
||||
batch_index // batch_size + 1,
|
||||
total_batches,
|
||||
len(data_rows),
|
||||
vector_dimension,
|
||||
)
|
||||
except (RuntimeError, ValueError, TypeError, ConnectionError):
|
||||
logger.exception("Parameterized SQL execution failed for %d documents", len(data_rows))
|
||||
logger.exception("SQL template: %s", insert_sql)
|
||||
logger.exception("Sample data row: %s", data_rows[0] if data_rows else "None")
|
||||
raise
|
||||
cursor.executemany(insert_sql, data_rows)
|
||||
logger.info(
|
||||
"Inserted batch %d/%d (%d valid docs using parameterized query with VECTOR(%d) cast)",
|
||||
batch_index // batch_size + 1,
|
||||
total_batches,
|
||||
len(data_rows),
|
||||
vector_dimension,
|
||||
)
|
||||
except (RuntimeError, ValueError, TypeError, ConnectionError):
|
||||
logger.exception("Parameterized SQL execution failed for %d documents", len(data_rows))
|
||||
logger.exception("SQL template: %s", insert_sql)
|
||||
logger.exception("Sample data row: %s", data_rows[0] if data_rows else "None")
|
||||
raise
|
||||
|
||||
@override
|
||||
def text_exists(self, id: str) -> bool:
|
||||
@@ -728,14 +725,13 @@ class ClickzettaVector(BaseVector):
|
||||
return False
|
||||
|
||||
safe_id = self._safe_doc_id(id)
|
||||
with self.get_connection_context() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
f"SELECT COUNT(*) FROM {self._config.schema_name}.{self._table_name} WHERE id = ?",
|
||||
binding_params=[safe_id],
|
||||
)
|
||||
result = cursor.fetchone()
|
||||
return result[0] > 0 if result else False
|
||||
with self.get_connection_context() as connection, connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
f"SELECT COUNT(*) FROM {self._config.schema_name}.{self._table_name} WHERE id = ?",
|
||||
binding_params=[safe_id],
|
||||
)
|
||||
result = cursor.fetchone()
|
||||
return result[0] > 0 if result else False
|
||||
|
||||
@override
|
||||
def delete_by_ids(self, ids: list[str]):
|
||||
@@ -759,9 +755,8 @@ class ClickzettaVector(BaseVector):
|
||||
placeholders = ",".join("?" for _ in safe_ids)
|
||||
sql = f"DELETE FROM {self._config.schema_name}.{self._table_name} WHERE id IN ({placeholders})"
|
||||
|
||||
with self.get_connection_context() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(sql, binding_params=safe_ids)
|
||||
with self.get_connection_context() as connection, connection.cursor() as cursor:
|
||||
cursor.execute(sql, binding_params=safe_ids)
|
||||
|
||||
@override
|
||||
def delete_by_metadata_field(self, key: str, value: str):
|
||||
@@ -776,16 +771,15 @@ class ClickzettaVector(BaseVector):
|
||||
|
||||
def _delete_by_metadata_field_impl(self, key: str, value: str):
|
||||
"""Implementation of delete by metadata field (executed in write worker thread)."""
|
||||
with self.get_connection_context() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
# Using JSON path to filter with parameterized query
|
||||
# Note: JSON path requires literal key name, cannot be parameterized
|
||||
# Use json_extract_string function for ClickZetta compatibility
|
||||
sql = (
|
||||
f"DELETE FROM {self._config.schema_name}.{self._table_name} "
|
||||
f"WHERE json_extract_string({Field.METADATA_KEY}, '$.{key}') = ?"
|
||||
)
|
||||
cursor.execute(sql, binding_params=[value])
|
||||
with self.get_connection_context() as connection, connection.cursor() as cursor:
|
||||
# Using JSON path to filter with parameterized query
|
||||
# Note: JSON path requires literal key name, cannot be parameterized
|
||||
# Use json_extract_string function for ClickZetta compatibility
|
||||
sql = (
|
||||
f"DELETE FROM {self._config.schema_name}.{self._table_name} "
|
||||
f"WHERE json_extract_string({Field.METADATA_KEY}, '$.{key}') = ?"
|
||||
)
|
||||
cursor.execute(sql, binding_params=[value])
|
||||
|
||||
@override
|
||||
def search_by_vector(self, query_vector: list[float], **kwargs: Any) -> list[Document]:
|
||||
@@ -845,31 +839,30 @@ class ClickzettaVector(BaseVector):
|
||||
"""
|
||||
|
||||
documents = []
|
||||
with self.get_connection_context() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
# Use hints parameter for vector search optimization
|
||||
search_hints = {
|
||||
"hints": {
|
||||
"sdk.job.timeout": 60, # Increase timeout for vector search
|
||||
"cz.sql.job.fast.mode": True,
|
||||
"cz.storage.parquet.vector.index.read.memory.cache": True,
|
||||
}
|
||||
with self.get_connection_context() as connection, connection.cursor() as cursor:
|
||||
# Use hints parameter for vector search optimization
|
||||
search_hints = {
|
||||
"hints": {
|
||||
"sdk.job.timeout": 60, # Increase timeout for vector search
|
||||
"cz.sql.job.fast.mode": True,
|
||||
"cz.storage.parquet.vector.index.read.memory.cache": True,
|
||||
}
|
||||
cursor.execute(search_sql, search_hints)
|
||||
results = cursor.fetchall()
|
||||
}
|
||||
cursor.execute(search_sql, search_hints)
|
||||
results = cursor.fetchall()
|
||||
|
||||
for row in results:
|
||||
# Parse metadata using centralized method
|
||||
metadata = self._parse_metadata(row[2], row[0])
|
||||
for row in results:
|
||||
# Parse metadata using centralized method
|
||||
metadata = self._parse_metadata(row[2], row[0])
|
||||
|
||||
# Add score based on distance
|
||||
if self._config.vector_distance_function == "cosine_distance":
|
||||
metadata["score"] = 1 - (row[3] / 2)
|
||||
else:
|
||||
metadata["score"] = 1 / (1 + row[3])
|
||||
# Add score based on distance
|
||||
if self._config.vector_distance_function == "cosine_distance":
|
||||
metadata["score"] = 1 - (row[3] / 2)
|
||||
else:
|
||||
metadata["score"] = 1 / (1 + row[3])
|
||||
|
||||
doc = Document(page_content=row[1], metadata=metadata)
|
||||
documents.append(doc)
|
||||
doc = Document(page_content=row[1], metadata=metadata)
|
||||
documents.append(doc)
|
||||
|
||||
return documents
|
||||
|
||||
@@ -922,58 +915,57 @@ class ClickzettaVector(BaseVector):
|
||||
"""
|
||||
|
||||
documents = []
|
||||
with self.get_connection_context() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
try:
|
||||
# Use hints parameter for full-text search optimization
|
||||
fulltext_hints = {
|
||||
"hints": {
|
||||
"sdk.job.timeout": 30, # Timeout for full-text search
|
||||
"cz.sql.job.fast.mode": True,
|
||||
"cz.sql.index.prewhere.enabled": True,
|
||||
}
|
||||
with self.get_connection_context() as connection, connection.cursor() as cursor:
|
||||
try:
|
||||
# Use hints parameter for full-text search optimization
|
||||
fulltext_hints = {
|
||||
"hints": {
|
||||
"sdk.job.timeout": 30, # Timeout for full-text search
|
||||
"cz.sql.job.fast.mode": True,
|
||||
"cz.sql.index.prewhere.enabled": True,
|
||||
}
|
||||
cursor.execute(search_sql, fulltext_hints)
|
||||
results = cursor.fetchall()
|
||||
}
|
||||
cursor.execute(search_sql, fulltext_hints)
|
||||
results = cursor.fetchall()
|
||||
|
||||
for row in results:
|
||||
# Parse metadata from JSON string (may be double-encoded)
|
||||
try:
|
||||
if row[2]:
|
||||
# First parse may yield a string (double-encoded JSON)
|
||||
first_pass = json.loads(row[2])
|
||||
for row in results:
|
||||
# Parse metadata from JSON string (may be double-encoded)
|
||||
try:
|
||||
if row[2]:
|
||||
# First parse may yield a string (double-encoded JSON)
|
||||
first_pass = json.loads(row[2])
|
||||
|
||||
match first_pass:
|
||||
case str():
|
||||
metadata = parse_metadata_json(first_pass)
|
||||
case dict():
|
||||
metadata = first_pass
|
||||
case _:
|
||||
metadata = {}
|
||||
else:
|
||||
metadata = {}
|
||||
except (json.JSONDecodeError, ValueError, TypeError):
|
||||
logger.exception("JSON parsing failed")
|
||||
# Fallback: extract document_id with regex
|
||||
match first_pass:
|
||||
case str():
|
||||
metadata = parse_metadata_json(first_pass)
|
||||
case dict():
|
||||
metadata = first_pass
|
||||
case _:
|
||||
metadata = {}
|
||||
else:
|
||||
metadata = {}
|
||||
except (json.JSONDecodeError, ValueError, TypeError):
|
||||
logger.exception("JSON parsing failed")
|
||||
# Fallback: extract document_id with regex
|
||||
|
||||
doc_id_match = re.search(r'"document_id":\s*"([^"]+)"', str(row[2] or ""))
|
||||
metadata = {"document_id": doc_id_match.group(1)} if doc_id_match else {}
|
||||
doc_id_match = re.search(r'"document_id":\s*"([^"]+)"', str(row[2] or ""))
|
||||
metadata = {"document_id": doc_id_match.group(1)} if doc_id_match else {}
|
||||
|
||||
# Ensure required fields are set
|
||||
metadata["doc_id"] = row[0] # segment id
|
||||
# Ensure required fields are set
|
||||
metadata["doc_id"] = row[0] # segment id
|
||||
|
||||
# Ensure document_id exists (critical for Dify's format_retrieval_documents)
|
||||
if "document_id" not in metadata:
|
||||
metadata["document_id"] = row[0] # fallback to segment id
|
||||
# Ensure document_id exists (critical for Dify's format_retrieval_documents)
|
||||
if "document_id" not in metadata:
|
||||
metadata["document_id"] = row[0] # fallback to segment id
|
||||
|
||||
# Add a relevance score for full-text search
|
||||
metadata["score"] = 1.0 # Clickzetta doesn't provide relevance scores
|
||||
doc = Document(page_content=row[1], metadata=metadata)
|
||||
documents.append(doc)
|
||||
except (RuntimeError, ValueError, TypeError, ConnectionError):
|
||||
logger.exception("Full-text search failed")
|
||||
# Fallback to LIKE search if full-text search fails
|
||||
return self._search_by_like(query, **kwargs)
|
||||
# Add a relevance score for full-text search
|
||||
metadata["score"] = 1.0 # Clickzetta doesn't provide relevance scores
|
||||
doc = Document(page_content=row[1], metadata=metadata)
|
||||
documents.append(doc)
|
||||
except (RuntimeError, ValueError, TypeError, ConnectionError):
|
||||
logger.exception("Full-text search failed")
|
||||
# Fallback to LIKE search if full-text search fails
|
||||
return self._search_by_like(query, **kwargs)
|
||||
|
||||
return documents
|
||||
|
||||
@@ -1019,34 +1011,32 @@ class ClickzettaVector(BaseVector):
|
||||
"""
|
||||
|
||||
documents = []
|
||||
with self.get_connection_context() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
# Use hints parameter for LIKE search optimization
|
||||
like_hints = {
|
||||
"hints": {
|
||||
"sdk.job.timeout": 20, # Timeout for LIKE search
|
||||
"cz.sql.job.fast.mode": True,
|
||||
}
|
||||
with self.get_connection_context() as connection, connection.cursor() as cursor:
|
||||
# Use hints parameter for LIKE search optimization
|
||||
like_hints = {
|
||||
"hints": {
|
||||
"sdk.job.timeout": 20, # Timeout for LIKE search
|
||||
"cz.sql.job.fast.mode": True,
|
||||
}
|
||||
cursor.execute(search_sql, like_hints)
|
||||
results = cursor.fetchall()
|
||||
}
|
||||
cursor.execute(search_sql, like_hints)
|
||||
results = cursor.fetchall()
|
||||
|
||||
for row in results:
|
||||
# Parse metadata using centralized method
|
||||
metadata = self._parse_metadata(row[2], row[0])
|
||||
for row in results:
|
||||
# Parse metadata using centralized method
|
||||
metadata = self._parse_metadata(row[2], row[0])
|
||||
|
||||
metadata["score"] = 0.5 # Lower score for LIKE search
|
||||
doc = Document(page_content=row[1], metadata=metadata)
|
||||
documents.append(doc)
|
||||
metadata["score"] = 0.5 # Lower score for LIKE search
|
||||
doc = Document(page_content=row[1], metadata=metadata)
|
||||
documents.append(doc)
|
||||
|
||||
return documents
|
||||
|
||||
@override
|
||||
def delete(self):
|
||||
"""Delete the entire collection."""
|
||||
with self.get_connection_context() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(f"DROP TABLE IF EXISTS {self._config.schema_name}.{self._table_name}")
|
||||
with self.get_connection_context() as connection, connection.cursor() as cursor:
|
||||
cursor.execute(f"DROP TABLE IF EXISTS {self._config.schema_name}.{self._table_name}")
|
||||
|
||||
def _format_vector_simple(self, vector: list[float]) -> str:
|
||||
"""Simple vector formatting for SQL queries."""
|
||||
|
||||
@@ -143,21 +143,27 @@ def test_init_client_connection_failures(elasticsearch_module):
|
||||
|
||||
client = MagicMock()
|
||||
client.ping.return_value = False
|
||||
with patch.object(elasticsearch_module, "Elasticsearch", return_value=client):
|
||||
with pytest.raises(ConnectionError, match="Failed to connect"):
|
||||
vector._init_client(_regular_config(elasticsearch_module))
|
||||
|
||||
with patch.object(
|
||||
elasticsearch_module,
|
||||
"Elasticsearch",
|
||||
side_effect=elasticsearch_module.ElasticsearchConnectionError("boom"),
|
||||
with (
|
||||
patch.object(elasticsearch_module, "Elasticsearch", return_value=client),
|
||||
pytest.raises(ConnectionError, match="Failed to connect"),
|
||||
):
|
||||
with pytest.raises(ConnectionError, match="Vector database connection error"):
|
||||
vector._init_client(_regular_config(elasticsearch_module))
|
||||
vector._init_client(_regular_config(elasticsearch_module))
|
||||
|
||||
with patch.object(elasticsearch_module, "Elasticsearch", side_effect=RuntimeError("oops")):
|
||||
with pytest.raises(ConnectionError, match="initialization failed"):
|
||||
vector._init_client(_regular_config(elasticsearch_module))
|
||||
with (
|
||||
patch.object(
|
||||
elasticsearch_module,
|
||||
"Elasticsearch",
|
||||
side_effect=elasticsearch_module.ElasticsearchConnectionError("boom"),
|
||||
),
|
||||
pytest.raises(ConnectionError, match="Vector database connection error"),
|
||||
):
|
||||
vector._init_client(_regular_config(elasticsearch_module))
|
||||
|
||||
with (
|
||||
patch.object(elasticsearch_module, "Elasticsearch", side_effect=RuntimeError("oops")),
|
||||
pytest.raises(ConnectionError, match="initialization failed"),
|
||||
):
|
||||
vector._init_client(_regular_config(elasticsearch_module))
|
||||
|
||||
|
||||
def test_init_get_version_and_check_version(elasticsearch_module):
|
||||
|
||||
@@ -239,9 +239,8 @@ def test_iris_vector_init_get_cursor_and_create(iris_module):
|
||||
cursor = MagicMock()
|
||||
conn.cursor.return_value = cursor
|
||||
vector.pool.get_connection.return_value = conn
|
||||
with pytest.raises(RuntimeError, match="boom"):
|
||||
with vector._get_cursor():
|
||||
raise RuntimeError("boom")
|
||||
with pytest.raises(RuntimeError, match="boom"), vector._get_cursor():
|
||||
raise RuntimeError("boom")
|
||||
conn.rollback.assert_called_once()
|
||||
|
||||
vector._create_collection = MagicMock()
|
||||
|
||||
@@ -201,9 +201,11 @@ def test_init_loads_fields_when_collection_exists(milvus_module):
|
||||
"fields": [{"name": "id"}, {"name": "content"}, {"name": "metadata"}, {"name": "sparse_vector"}]
|
||||
}
|
||||
|
||||
with patch.object(milvus_module.MilvusVector, "_init_client", return_value=client):
|
||||
with patch.object(milvus_module.MilvusVector, "_check_hybrid_search_support", return_value=False):
|
||||
vector = milvus_module.MilvusVector("collection_1", _config(milvus_module))
|
||||
with (
|
||||
patch.object(milvus_module.MilvusVector, "_init_client", return_value=client),
|
||||
patch.object(milvus_module.MilvusVector, "_check_hybrid_search_support", return_value=False),
|
||||
):
|
||||
vector = milvus_module.MilvusVector("collection_1", _config(milvus_module))
|
||||
|
||||
assert "id" not in vector._fields
|
||||
assert "content" in vector._fields
|
||||
|
||||
@@ -437,12 +437,11 @@ class OceanBaseVector(BaseVector):
|
||||
ORDER BY score DESC
|
||||
LIMIT {top_k}"""
|
||||
|
||||
with self._client.engine.connect() as conn:
|
||||
with conn.begin():
|
||||
result = conn.execute(text(full_sql), params)
|
||||
rows = result.fetchall()
|
||||
with self._client.engine.connect() as conn, conn.begin():
|
||||
result = conn.execute(text(full_sql), params)
|
||||
rows = result.fetchall()
|
||||
|
||||
return self._process_search_results(rows, score_threshold=score_threshold)
|
||||
return self._process_search_results(rows, score_threshold=score_threshold)
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
"Failed to perform full-text search on collection '%s' with query '%s'",
|
||||
|
||||
@@ -85,9 +85,8 @@ class PluginMigration:
|
||||
try:
|
||||
plugins = cls.extract_installed_plugin_ids(tenant_id)
|
||||
# Use lock when writing to file
|
||||
with file_lock:
|
||||
with open(filepath, "a") as f:
|
||||
f.write(json.dumps({"tenant_id": tenant_id, "plugins": plugins}) + "\n")
|
||||
with file_lock, open(filepath, "a") as f:
|
||||
f.write(json.dumps({"tenant_id": tenant_id, "plugins": plugins}) + "\n")
|
||||
|
||||
# Use lock when updating counter
|
||||
with counter_lock:
|
||||
|
||||
@@ -10,8 +10,9 @@ from urllib.parse import urlparse
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import or_, select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
from core.db.session_factory import session_factory as default_session_factory
|
||||
from core.entities.mcp_provider import IdentityMode, MCPAuthentication, MCPConfiguration, MCPProviderEntity
|
||||
from core.helper import encrypter
|
||||
from core.helper.provider_cache import NoOpProviderCredentialCache
|
||||
@@ -346,6 +347,31 @@ class MCPToolManageService:
|
||||
|
||||
# ========== OAuth and Credentials Operations ==========
|
||||
|
||||
@classmethod
|
||||
def update_provider_credentials_in_new_transaction(
|
||||
cls,
|
||||
*,
|
||||
provider_id: str,
|
||||
tenant_id: str,
|
||||
credentials: dict[str, Any],
|
||||
authed: bool | None = None,
|
||||
session_maker: sessionmaker[Session] | None = None,
|
||||
) -> None:
|
||||
"""Update provider credentials in a service-owned transaction.
|
||||
|
||||
Callers can perform remote MCP operations before invoking this method
|
||||
without holding a database transaction open during network I/O. An
|
||||
alternate session maker may be injected for isolated databases.
|
||||
"""
|
||||
session_maker = session_maker or default_session_factory.get_session_maker()
|
||||
with session_maker.begin() as session:
|
||||
cls(session=session).update_provider_credentials(
|
||||
provider_id=provider_id,
|
||||
tenant_id=tenant_id,
|
||||
credentials=credentials,
|
||||
authed=authed,
|
||||
)
|
||||
|
||||
def update_provider_credentials(
|
||||
self, *, provider_id: str, tenant_id: str, credentials: dict[str, Any], authed: bool | None = None
|
||||
) -> None:
|
||||
|
||||
@@ -87,9 +87,8 @@ def setup_account(request) -> Generator[Account, None, None]:
|
||||
session=db.session(),
|
||||
)
|
||||
|
||||
with _CACHED_APP.test_request_context():
|
||||
with Session(bind=db.engine, expire_on_commit=False) as session:
|
||||
account = session.scalars(select(Account).filter_by(email=email)).one()
|
||||
with _CACHED_APP.test_request_context(), Session(bind=db.engine, expire_on_commit=False) as session:
|
||||
account = session.scalars(select(Account).filter_by(email=email)).one()
|
||||
|
||||
yield account
|
||||
|
||||
|
||||
@@ -120,17 +120,16 @@ class TestConversationListApi:
|
||||
"pagination_by_last_id",
|
||||
side_effect=LastConversationNotExistsError(),
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, user, chat_app)
|
||||
method(api, user, chat_app)
|
||||
|
||||
def test_wrong_app_mode(self, app: Flask, non_chat_app: InstalledApp, user: Account) -> None:
|
||||
api = conversation_module.ConversationListApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
with app.test_request_context("/"):
|
||||
with pytest.raises(NotChatAppError):
|
||||
method(api, user, non_chat_app)
|
||||
with app.test_request_context("/"), pytest.raises(NotChatAppError):
|
||||
method(api, user, non_chat_app)
|
||||
|
||||
|
||||
class TestConversationApi:
|
||||
@@ -166,17 +165,16 @@ class TestConversationApi:
|
||||
"delete",
|
||||
side_effect=ConversationNotExistsError(),
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, user, chat_app, "cid")
|
||||
method(api, user, chat_app, "cid")
|
||||
|
||||
def test_delete_wrong_app_mode(self, app: Flask, non_chat_app: InstalledApp, user: Account) -> None:
|
||||
api = conversation_module.ConversationApi()
|
||||
method = unwrap(api.delete)
|
||||
|
||||
with app.test_request_context("/"):
|
||||
with pytest.raises(NotChatAppError):
|
||||
method(api, user, non_chat_app, "cid")
|
||||
with app.test_request_context("/"), pytest.raises(NotChatAppError):
|
||||
method(api, user, non_chat_app, "cid")
|
||||
|
||||
|
||||
class TestConversationRenameApi:
|
||||
@@ -219,15 +217,15 @@ class TestConversationRenameApi:
|
||||
"rename",
|
||||
side_effect=ConversationNotExistsError(),
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
conversation_module.ConversationRenamePayload.model_validate({"name": "new"}),
|
||||
user,
|
||||
chat_app,
|
||||
"cid",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
conversation_module.ConversationRenamePayload.model_validate({"name": "new"}),
|
||||
user,
|
||||
chat_app,
|
||||
"cid",
|
||||
)
|
||||
|
||||
|
||||
class TestConversationPinApi:
|
||||
|
||||
@@ -181,6 +181,6 @@ class TestAppApiKeyResource:
|
||||
with (
|
||||
flask_app_with_containers.test_request_context("/"),
|
||||
patch("controllers.console.apikey._get_resource"),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
BaseApiKeyResource.delete(resource, "rid", "kid", "tenant-id", non_admin)
|
||||
BaseApiKeyResource.delete(resource, "rid", "kid", "tenant-id", non_admin)
|
||||
|
||||
@@ -144,9 +144,8 @@ class TestMemberCancelInviteApiWithContainers:
|
||||
factory = WorkspaceMembersIntegrationFactory
|
||||
tenant, current_user = factory.create_owner_workspace(db_session_with_containers)
|
||||
|
||||
with flask_app_with_containers.test_request_context("/"):
|
||||
with pytest.raises(HTTPException):
|
||||
method(api, current_user, str(uuid4()))
|
||||
with flask_app_with_containers.test_request_context("/"), pytest.raises(HTTPException):
|
||||
method(api, current_user, str(uuid4()))
|
||||
|
||||
def test_cancel_cannot_operate_self(
|
||||
self, flask_app_with_containers: Flask, db_session_with_containers: Session
|
||||
@@ -245,9 +244,8 @@ class TestMemberUpdateRoleApiWithContainers:
|
||||
factory = WorkspaceMembersIntegrationFactory
|
||||
tenant, current_user = factory.create_owner_workspace(db_session_with_containers)
|
||||
|
||||
with flask_app_with_containers.test_request_context("/", json={"role": "normal"}):
|
||||
with pytest.raises(HTTPException):
|
||||
method(api, current_user, str(uuid4()))
|
||||
with flask_app_with_containers.test_request_context("/", json={"role": "normal"}), pytest.raises(HTTPException):
|
||||
method(api, current_user, str(uuid4()))
|
||||
|
||||
|
||||
class TestOwnerTransferApiWithContainers:
|
||||
@@ -259,9 +257,11 @@ class TestOwnerTransferApiWithContainers:
|
||||
member = factory.create_account(db_session_with_containers, email_prefix="member")
|
||||
token = factory.create_owner_transfer_token(current_user)
|
||||
|
||||
with flask_app_with_containers.test_request_context("/", json={"token": token}):
|
||||
with pytest.raises(MemberNotInTenantError):
|
||||
method(api, current_user, member.id)
|
||||
with (
|
||||
flask_app_with_containers.test_request_context("/", json={"token": token}),
|
||||
pytest.raises(MemberNotInTenantError),
|
||||
):
|
||||
method(api, current_user, member.id)
|
||||
|
||||
def test_member_not_found(self, flask_app_with_containers: Flask, db_session_with_containers: Session) -> None:
|
||||
api = OwnerTransfer()
|
||||
@@ -270,9 +270,8 @@ class TestOwnerTransferApiWithContainers:
|
||||
tenant, current_user = factory.create_owner_workspace(db_session_with_containers)
|
||||
token = factory.create_owner_transfer_token(current_user)
|
||||
|
||||
with flask_app_with_containers.test_request_context("/", json={"token": token}):
|
||||
with pytest.raises(HTTPException):
|
||||
method(api, current_user, str(uuid4()))
|
||||
with flask_app_with_containers.test_request_context("/", json={"token": token}), pytest.raises(HTTPException):
|
||||
method(api, current_user, str(uuid4()))
|
||||
|
||||
def test_transfer_success(self, flask_app_with_containers: Flask, db_session_with_containers: Session) -> None:
|
||||
api = OwnerTransfer()
|
||||
|
||||
@@ -50,14 +50,16 @@ class TestSessionList:
|
||||
mint = _mint_account_token(db_session_with_containers, account, device_label="Laptop")
|
||||
|
||||
api = AccountSessionsApi()
|
||||
with app.test_request_context("/openapi/v1/account/sessions"):
|
||||
with account_auth_context(account, token_id=mint.token_id):
|
||||
result = unwrap(api.get)(
|
||||
api,
|
||||
db_session_with_containers,
|
||||
auth_data=auth_for(account, token_id=mint.token_id),
|
||||
query=SessionListQuery(),
|
||||
)
|
||||
with (
|
||||
app.test_request_context("/openapi/v1/account/sessions"),
|
||||
account_auth_context(account, token_id=mint.token_id),
|
||||
):
|
||||
result = unwrap(api.get)(
|
||||
api,
|
||||
db_session_with_containers,
|
||||
auth_data=auth_for(account, token_id=mint.token_id),
|
||||
query=SessionListQuery(),
|
||||
)
|
||||
|
||||
assert result.total == 1
|
||||
row = result.data[0]
|
||||
@@ -75,14 +77,16 @@ class TestSessionList:
|
||||
_mint_account_token(db_session_with_containers, other)
|
||||
|
||||
api = AccountSessionsApi()
|
||||
with app.test_request_context("/openapi/v1/account/sessions"):
|
||||
with account_auth_context(account, token_id=mine.token_id):
|
||||
result = unwrap(api.get)(
|
||||
api,
|
||||
db_session_with_containers,
|
||||
auth_data=auth_for(account, token_id=mine.token_id),
|
||||
query=SessionListQuery(),
|
||||
)
|
||||
with (
|
||||
app.test_request_context("/openapi/v1/account/sessions"),
|
||||
account_auth_context(account, token_id=mine.token_id),
|
||||
):
|
||||
result = unwrap(api.get)(
|
||||
api,
|
||||
db_session_with_containers,
|
||||
auth_data=auth_for(account, token_id=mine.token_id),
|
||||
query=SessionListQuery(),
|
||||
)
|
||||
|
||||
assert {row.id for row in result.data} == {str(mine.token_id)}
|
||||
|
||||
@@ -95,24 +99,28 @@ class TestSessionRevoke:
|
||||
mint = _mint_account_token(db_session_with_containers, account)
|
||||
|
||||
revoke_api = AccountSessionsSelfApi()
|
||||
with app.test_request_context("/openapi/v1/account/sessions/self", method="DELETE"):
|
||||
with account_auth_context(account, token_id=mint.token_id):
|
||||
result = unwrap(revoke_api.delete)(
|
||||
revoke_api, db_session_with_containers, auth_data=auth_for(account, token_id=mint.token_id)
|
||||
)
|
||||
with (
|
||||
app.test_request_context("/openapi/v1/account/sessions/self", method="DELETE"),
|
||||
account_auth_context(account, token_id=mint.token_id),
|
||||
):
|
||||
result = unwrap(revoke_api.delete)(
|
||||
revoke_api, db_session_with_containers, auth_data=auth_for(account, token_id=mint.token_id)
|
||||
)
|
||||
|
||||
assert result.status == "revoked"
|
||||
|
||||
# Revocation persisted: the real list path no longer returns it.
|
||||
list_api = AccountSessionsApi()
|
||||
with app.test_request_context("/openapi/v1/account/sessions"):
|
||||
with account_auth_context(account, token_id=mint.token_id):
|
||||
listing = unwrap(list_api.get)(
|
||||
list_api,
|
||||
db_session_with_containers,
|
||||
auth_data=auth_for(account, token_id=mint.token_id),
|
||||
query=SessionListQuery(),
|
||||
)
|
||||
with (
|
||||
app.test_request_context("/openapi/v1/account/sessions"),
|
||||
account_auth_context(account, token_id=mint.token_id),
|
||||
):
|
||||
listing = unwrap(list_api.get)(
|
||||
list_api,
|
||||
db_session_with_containers,
|
||||
auth_data=auth_for(account, token_id=mint.token_id),
|
||||
query=SessionListQuery(),
|
||||
)
|
||||
assert listing.total == 0
|
||||
|
||||
def test_revoke_by_id_for_own_session(
|
||||
@@ -123,14 +131,16 @@ class TestSessionRevoke:
|
||||
session_id = str(mint.token_id)
|
||||
|
||||
api = AccountSessionByIdApi()
|
||||
with app.test_request_context(f"/openapi/v1/account/sessions/{session_id}", method="DELETE"):
|
||||
with account_auth_context(account, token_id=mint.token_id):
|
||||
result = unwrap(api.delete)(
|
||||
api,
|
||||
db_session_with_containers,
|
||||
session_id=session_id,
|
||||
auth_data=auth_for(account, token_id=mint.token_id),
|
||||
)
|
||||
with (
|
||||
app.test_request_context(f"/openapi/v1/account/sessions/{session_id}", method="DELETE"),
|
||||
account_auth_context(account, token_id=mint.token_id),
|
||||
):
|
||||
result = unwrap(api.delete)(
|
||||
api,
|
||||
db_session_with_containers,
|
||||
session_id=session_id,
|
||||
auth_data=auth_for(account, token_id=mint.token_id),
|
||||
)
|
||||
|
||||
assert result.status == "revoked"
|
||||
|
||||
@@ -145,12 +155,14 @@ class TestSessionRevoke:
|
||||
|
||||
api = AccountSessionByIdApi()
|
||||
session_id = str(foreign.token_id)
|
||||
with app.test_request_context(f"/openapi/v1/account/sessions/{session_id}", method="DELETE"):
|
||||
with account_auth_context(outsider, token_id=uuid4()):
|
||||
with pytest.raises(NotFound):
|
||||
unwrap(api.delete)(
|
||||
api,
|
||||
db_session_with_containers,
|
||||
session_id=session_id,
|
||||
auth_data=auth_for(outsider, token_id=uuid4()),
|
||||
)
|
||||
with (
|
||||
app.test_request_context(f"/openapi/v1/account/sessions/{session_id}", method="DELETE"),
|
||||
account_auth_context(outsider, token_id=uuid4()),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
unwrap(api.delete)(
|
||||
api,
|
||||
db_session_with_containers,
|
||||
session_id=session_id,
|
||||
auth_data=auth_for(outsider, token_id=uuid4()),
|
||||
)
|
||||
|
||||
@@ -151,15 +151,14 @@ class TestAppDescribe:
|
||||
missing_id = str(uuid4())
|
||||
|
||||
api = AppDescribeApi()
|
||||
with app.test_request_context(f"/openapi/v1/apps/{missing_id}"):
|
||||
with pytest.raises(NotFound):
|
||||
unwrap(api.get)(
|
||||
api,
|
||||
db_session_with_containers,
|
||||
app_id=missing_id,
|
||||
auth_data=auth_for(account),
|
||||
query=AppDescribeQuery(),
|
||||
)
|
||||
with app.test_request_context(f"/openapi/v1/apps/{missing_id}"), pytest.raises(NotFound):
|
||||
unwrap(api.get)(
|
||||
api,
|
||||
db_session_with_containers,
|
||||
app_id=missing_id,
|
||||
auth_data=auth_for(account),
|
||||
query=AppDescribeQuery(),
|
||||
)
|
||||
|
||||
def test_describe_api_disabled_app_is_404(
|
||||
self, app: Flask, db_session_with_containers: Session, make_account: Callable[..., Account]
|
||||
@@ -170,12 +169,11 @@ class TestAppDescribe:
|
||||
hidden = _create_app(db_session_with_containers, account, name="Hidden", enable_api=False)
|
||||
|
||||
api = AppDescribeApi()
|
||||
with app.test_request_context(f"/openapi/v1/apps/{hidden.id}"):
|
||||
with pytest.raises(NotFound):
|
||||
unwrap(api.get)(
|
||||
api,
|
||||
db_session_with_containers,
|
||||
app_id=hidden.id,
|
||||
auth_data=auth_for(account),
|
||||
query=AppDescribeQuery(),
|
||||
)
|
||||
with app.test_request_context(f"/openapi/v1/apps/{hidden.id}"), pytest.raises(NotFound):
|
||||
unwrap(api.get)(
|
||||
api,
|
||||
db_session_with_containers,
|
||||
app_id=hidden.id,
|
||||
auth_data=auth_for(account),
|
||||
query=AppDescribeQuery(),
|
||||
)
|
||||
|
||||
@@ -80,11 +80,10 @@ class TestWorkspaceDetail:
|
||||
assert someone_elses_ws is not None
|
||||
|
||||
api = WorkspaceByIdApi()
|
||||
with app.test_request_context(f"/openapi/v1/workspaces/{someone_elses_ws.id}"):
|
||||
with pytest.raises(NotFound):
|
||||
unwrap(api.get)(
|
||||
api, db_session_with_containers, workspace_id=someone_elses_ws.id, auth_data=auth_for(outsider)
|
||||
)
|
||||
with app.test_request_context(f"/openapi/v1/workspaces/{someone_elses_ws.id}"), pytest.raises(NotFound):
|
||||
unwrap(api.get)(
|
||||
api, db_session_with_containers, workspace_id=someone_elses_ws.id, auth_data=auth_for(outsider)
|
||||
)
|
||||
|
||||
|
||||
class TestWorkspaceSwitch:
|
||||
@@ -126,8 +125,8 @@ class TestWorkspaceSwitch:
|
||||
assert outsider_ws is not None
|
||||
|
||||
api = WorkspaceSwitchApi()
|
||||
with app.test_request_context(f"/openapi/v1/workspaces/{outsider_ws.id}:switch", method="POST"):
|
||||
with pytest.raises(NotFound):
|
||||
unwrap(api.post)(
|
||||
api, db_session_with_containers, workspace_id=outsider_ws.id, auth_data=auth_for(account)
|
||||
)
|
||||
with (
|
||||
app.test_request_context(f"/openapi/v1/workspaces/{outsider_ws.id}:switch", method="POST"),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
unwrap(api.post)(api, db_session_with_containers, workspace_id=outsider_ws.id, auth_data=auth_for(account))
|
||||
|
||||
@@ -39,9 +39,8 @@ class TestConversationListApi:
|
||||
return flask_app_with_containers
|
||||
|
||||
def test_non_chat_mode_raises(self, app: Flask) -> None:
|
||||
with app.test_request_context("/conversations"):
|
||||
with pytest.raises(NotChatAppError):
|
||||
ConversationListApi().get(_completion_app(), _end_user())
|
||||
with app.test_request_context("/conversations"), pytest.raises(NotChatAppError):
|
||||
ConversationListApi().get(_completion_app(), _end_user())
|
||||
|
||||
@patch("controllers.web.conversation.WebConversationService.pagination_by_last_id")
|
||||
def test_happy_path(self, mock_paginate: MagicMock, app: Flask) -> None:
|
||||
@@ -70,9 +69,8 @@ class TestConversationApi:
|
||||
return flask_app_with_containers
|
||||
|
||||
def test_non_chat_mode_raises(self, app: Flask) -> None:
|
||||
with app.test_request_context(f"/conversations/{uuid4()}"):
|
||||
with pytest.raises(NotChatAppError):
|
||||
ConversationApi().delete(_completion_app(), _end_user(), uuid4())
|
||||
with app.test_request_context(f"/conversations/{uuid4()}"), pytest.raises(NotChatAppError):
|
||||
ConversationApi().delete(_completion_app(), _end_user(), uuid4())
|
||||
|
||||
@patch("controllers.web.conversation.ConversationService.delete")
|
||||
def test_delete_success(self, mock_delete: MagicMock, app: Flask) -> None:
|
||||
@@ -86,9 +84,11 @@ class TestConversationApi:
|
||||
@patch("controllers.web.conversation.ConversationService.delete", side_effect=ConversationNotExistsError())
|
||||
def test_delete_not_found(self, mock_delete: MagicMock, app: Flask) -> None:
|
||||
c_id = uuid4()
|
||||
with app.test_request_context(f"/conversations/{c_id}"):
|
||||
with pytest.raises(NotFound, match="Conversation Not Exists"):
|
||||
ConversationApi().delete(_chat_app(), _end_user(), c_id)
|
||||
with (
|
||||
app.test_request_context(f"/conversations/{c_id}"),
|
||||
pytest.raises(NotFound, match="Conversation Not Exists"),
|
||||
):
|
||||
ConversationApi().delete(_chat_app(), _end_user(), c_id)
|
||||
|
||||
|
||||
class TestConversationRenameApi:
|
||||
@@ -97,9 +97,11 @@ class TestConversationRenameApi:
|
||||
return flask_app_with_containers
|
||||
|
||||
def test_non_chat_mode_raises(self, app: Flask) -> None:
|
||||
with app.test_request_context(f"/conversations/{uuid4()}/name", method="POST", json={"name": "x"}):
|
||||
with pytest.raises(NotChatAppError):
|
||||
ConversationRenameApi().post(_completion_app(), _end_user(), uuid4())
|
||||
with (
|
||||
app.test_request_context(f"/conversations/{uuid4()}/name", method="POST", json={"name": "x"}),
|
||||
pytest.raises(NotChatAppError),
|
||||
):
|
||||
ConversationRenameApi().post(_completion_app(), _end_user(), uuid4())
|
||||
|
||||
@patch("controllers.web.conversation.ConversationService.rename")
|
||||
def test_rename_success(self, mock_rename: MagicMock, app: Flask) -> None:
|
||||
@@ -127,9 +129,11 @@ class TestConversationRenameApi:
|
||||
def test_rename_not_found(self, mock_rename: MagicMock, app: Flask) -> None:
|
||||
c_id = uuid4()
|
||||
|
||||
with app.test_request_context(f"/conversations/{c_id}/name", method="POST", json={"name": "X"}):
|
||||
with pytest.raises(NotFound, match="Conversation Not Exists"):
|
||||
ConversationRenameApi().post(_chat_app(), _end_user(), c_id)
|
||||
with (
|
||||
app.test_request_context(f"/conversations/{c_id}/name", method="POST", json={"name": "X"}),
|
||||
pytest.raises(NotFound, match="Conversation Not Exists"),
|
||||
):
|
||||
ConversationRenameApi().post(_chat_app(), _end_user(), c_id)
|
||||
|
||||
|
||||
class TestConversationPinApi:
|
||||
@@ -138,9 +142,8 @@ class TestConversationPinApi:
|
||||
return flask_app_with_containers
|
||||
|
||||
def test_non_chat_mode_raises(self, app: Flask) -> None:
|
||||
with app.test_request_context(f"/conversations/{uuid4()}/pin", method="PATCH"):
|
||||
with pytest.raises(NotChatAppError):
|
||||
ConversationPinApi().patch(_completion_app(), _end_user(), uuid4())
|
||||
with app.test_request_context(f"/conversations/{uuid4()}/pin", method="PATCH"), pytest.raises(NotChatAppError):
|
||||
ConversationPinApi().patch(_completion_app(), _end_user(), uuid4())
|
||||
|
||||
@patch("controllers.web.conversation.WebConversationService.pin")
|
||||
def test_pin_success(self, mock_pin: MagicMock, app: Flask) -> None:
|
||||
@@ -153,9 +156,8 @@ class TestConversationPinApi:
|
||||
@patch("controllers.web.conversation.WebConversationService.pin", side_effect=ConversationNotExistsError())
|
||||
def test_pin_not_found(self, mock_pin: MagicMock, app: Flask) -> None:
|
||||
c_id = uuid4()
|
||||
with app.test_request_context(f"/conversations/{c_id}/pin", method="PATCH"):
|
||||
with pytest.raises(NotFound):
|
||||
ConversationPinApi().patch(_chat_app(), _end_user(), c_id)
|
||||
with app.test_request_context(f"/conversations/{c_id}/pin", method="PATCH"), pytest.raises(NotFound):
|
||||
ConversationPinApi().patch(_chat_app(), _end_user(), c_id)
|
||||
|
||||
|
||||
class TestConversationUnPinApi:
|
||||
@@ -164,9 +166,11 @@ class TestConversationUnPinApi:
|
||||
return flask_app_with_containers
|
||||
|
||||
def test_non_chat_mode_raises(self, app: Flask) -> None:
|
||||
with app.test_request_context(f"/conversations/{uuid4()}/unpin", method="PATCH"):
|
||||
with pytest.raises(NotChatAppError):
|
||||
ConversationUnPinApi().patch(_completion_app(), _end_user(), uuid4())
|
||||
with (
|
||||
app.test_request_context(f"/conversations/{uuid4()}/unpin", method="PATCH"),
|
||||
pytest.raises(NotChatAppError),
|
||||
):
|
||||
ConversationUnPinApi().patch(_completion_app(), _end_user(), uuid4())
|
||||
|
||||
@patch("controllers.web.conversation.WebConversationService.unpin")
|
||||
def test_unpin_success(self, mock_unpin: MagicMock, app: Flask) -> None:
|
||||
|
||||
@@ -103,9 +103,8 @@ class TestAppSiteApi:
|
||||
app_model = _create_app(db_session_with_containers, tenant.id)
|
||||
end_user = _end_user(tenant.id, app_model.id)
|
||||
|
||||
with app.test_request_context("/site"):
|
||||
with pytest.raises(Forbidden):
|
||||
AppSiteApi().get(app_model, end_user)
|
||||
with app.test_request_context("/site"), pytest.raises(Forbidden):
|
||||
AppSiteApi().get(app_model, end_user)
|
||||
|
||||
def test_archived_tenant_raises_forbidden(self, app: Flask, db_session_with_containers: Session) -> None:
|
||||
app.config["RESTX_MASK_HEADER"] = "X-Fields"
|
||||
@@ -114,9 +113,8 @@ class TestAppSiteApi:
|
||||
_create_site(db_session_with_containers, app_model.id)
|
||||
end_user = _end_user(tenant.id, app_model.id)
|
||||
|
||||
with app.test_request_context("/site"):
|
||||
with pytest.raises(Forbidden):
|
||||
AppSiteApi().get(app_model, end_user)
|
||||
with app.test_request_context("/site"), pytest.raises(Forbidden):
|
||||
AppSiteApi().get(app_model, end_user)
|
||||
|
||||
|
||||
def _tenant_model(*, plan: str = "basic", custom_config: TenantCustomConfigDict | None = None) -> Tenant:
|
||||
|
||||
@@ -268,9 +268,8 @@ class TestDecodeJwtToken:
|
||||
mock_features.return_value = False
|
||||
mock_extract.return_value = None
|
||||
|
||||
with app.test_request_context("/", headers={"X-App-Code": "code1"}):
|
||||
with pytest.raises(Unauthorized):
|
||||
decode_jwt_token()
|
||||
with app.test_request_context("/", headers={"X-App-Code": "code1"}), pytest.raises(Unauthorized):
|
||||
decode_jwt_token()
|
||||
|
||||
@patch("controllers.web.wraps.SystemFeatureService.is_webapp_auth_enabled")
|
||||
@patch("controllers.web.wraps.PassportService")
|
||||
@@ -291,9 +290,8 @@ class TestDecodeJwtToken:
|
||||
}
|
||||
mock_features.return_value = False
|
||||
|
||||
with app.test_request_context("/", headers={"X-App-Code": "code1"}):
|
||||
with pytest.raises(NotFound):
|
||||
decode_jwt_token()
|
||||
with app.test_request_context("/", headers={"X-App-Code": "code1"}), pytest.raises(NotFound):
|
||||
decode_jwt_token()
|
||||
|
||||
@patch("controllers.web.wraps.SystemFeatureService.is_webapp_auth_enabled")
|
||||
@patch("controllers.web.wraps.PassportService")
|
||||
@@ -316,9 +314,11 @@ class TestDecodeJwtToken:
|
||||
}
|
||||
mock_features.return_value = False
|
||||
|
||||
with app.test_request_context("/", headers={"X-App-Code": site.code}):
|
||||
with pytest.raises(BadRequest, match="Site is disabled"):
|
||||
decode_jwt_token()
|
||||
with (
|
||||
app.test_request_context("/", headers={"X-App-Code": site.code}),
|
||||
pytest.raises(BadRequest, match="Site is disabled"),
|
||||
):
|
||||
decode_jwt_token()
|
||||
|
||||
@patch("controllers.web.wraps.SystemFeatureService.is_webapp_auth_enabled")
|
||||
@patch("controllers.web.wraps.PassportService")
|
||||
@@ -342,9 +342,8 @@ class TestDecodeJwtToken:
|
||||
}
|
||||
mock_features.return_value = False
|
||||
|
||||
with app.test_request_context("/", headers={"X-App-Code": site.code}):
|
||||
with pytest.raises(NotFound):
|
||||
decode_jwt_token()
|
||||
with app.test_request_context("/", headers={"X-App-Code": site.code}), pytest.raises(NotFound):
|
||||
decode_jwt_token()
|
||||
|
||||
@patch("controllers.web.wraps.SystemFeatureService.is_webapp_auth_enabled")
|
||||
@patch("controllers.web.wraps.PassportService")
|
||||
@@ -367,6 +366,8 @@ class TestDecodeJwtToken:
|
||||
}
|
||||
mock_features.return_value = False
|
||||
|
||||
with app.test_request_context("/", headers={"X-App-Code": site.code}):
|
||||
with pytest.raises(Unauthorized, match="expired"):
|
||||
decode_jwt_token(user_id="different-user")
|
||||
with (
|
||||
app.test_request_context("/", headers={"X-App-Code": site.code}),
|
||||
pytest.raises(Unauthorized, match="expired"),
|
||||
):
|
||||
decode_jwt_token(user_id="different-user")
|
||||
|
||||
@@ -514,14 +514,16 @@ class TestKnowledgeRetrievalIntegration:
|
||||
dataset_retrieval = DatasetRetrieval()
|
||||
|
||||
# Mock rate limit check and retrieval
|
||||
with patch.object(dataset_retrieval, "_check_knowledge_rate_limit"):
|
||||
with patch.object(dataset_retrieval, "get_metadata_filter_condition", return_value=(None, None)):
|
||||
with patch.object(dataset_retrieval, "multiple_retrieve", return_value=[]):
|
||||
# Act
|
||||
result = dataset_retrieval.knowledge_retrieval(db_session_with_containers, request)
|
||||
with (
|
||||
patch.object(dataset_retrieval, "_check_knowledge_rate_limit"),
|
||||
patch.object(dataset_retrieval, "get_metadata_filter_condition", return_value=(None, None)),
|
||||
patch.object(dataset_retrieval, "multiple_retrieve", return_value=[]),
|
||||
):
|
||||
# Act
|
||||
result = dataset_retrieval.knowledge_retrieval(db_session_with_containers, request)
|
||||
|
||||
# Assert
|
||||
assert isinstance(result, list)
|
||||
# Assert
|
||||
assert isinstance(result, list)
|
||||
|
||||
def test_knowledge_retrieval_no_available_datasets(
|
||||
self, db_session_with_containers: Session, mock_external_service_dependencies
|
||||
@@ -613,14 +615,15 @@ class TestKnowledgeRetrievalIntegration:
|
||||
dataset_retrieval = DatasetRetrieval()
|
||||
|
||||
# Mock rate limit check to raise exception
|
||||
with patch.object(
|
||||
dataset_retrieval,
|
||||
"_check_knowledge_rate_limit",
|
||||
side_effect=Exception("Rate limit exceeded"),
|
||||
with (
|
||||
patch.object(
|
||||
dataset_retrieval,
|
||||
"_check_knowledge_rate_limit",
|
||||
side_effect=Exception("Rate limit exceeded"),
|
||||
),
|
||||
pytest.raises(Exception, match="Rate limit exceeded"),
|
||||
):
|
||||
# Act & Assert
|
||||
with pytest.raises(Exception, match="Rate limit exceeded"):
|
||||
dataset_retrieval.knowledge_retrieval(db_session_with_containers, request)
|
||||
dataset_retrieval.knowledge_retrieval(db_session_with_containers, request)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -189,9 +189,8 @@ class TestEnumText:
|
||||
),
|
||||
]
|
||||
for idx, c in enumerate(cases, 1):
|
||||
with pytest.raises(sa_exc.StatementError) as exc:
|
||||
with Session(engine_with_containers) as session:
|
||||
c.action(session)
|
||||
with pytest.raises(sa_exc.StatementError) as exc, Session(engine_with_containers) as session:
|
||||
c.action(session)
|
||||
|
||||
assert isinstance(exc.value.orig, c.exc_type), f"test case {idx} failed, name={c.name}"
|
||||
|
||||
@@ -204,9 +203,8 @@ class TestEnumText:
|
||||
session.execute(sa.text(insertion_sql))
|
||||
session.commit()
|
||||
|
||||
with pytest.raises(ValueError) as exc:
|
||||
with Session(engine_with_containers) as session:
|
||||
_user = session.scalar(select(_User).where(_User.id == 1).limit(1))
|
||||
with pytest.raises(ValueError) as exc, Session(engine_with_containers) as session:
|
||||
_user = session.scalar(select(_User).where(_User.id == 1).limit(1))
|
||||
|
||||
assert str(exc.value) == "'invalid' is not a valid _UserType"
|
||||
|
||||
@@ -222,8 +220,7 @@ class TestEnumText:
|
||||
session.commit()
|
||||
|
||||
for record_id, legacy_value in enumerate(("text-generation", "embeddings", "reranking"), 1):
|
||||
with pytest.raises(ValueError) as exc:
|
||||
with Session(engine_with_containers) as session:
|
||||
session.scalar(select(_LegacyModelTypeRecord).where(_LegacyModelTypeRecord.id == record_id))
|
||||
with pytest.raises(ValueError) as exc, Session(engine_with_containers) as session:
|
||||
session.scalar(select(_LegacyModelTypeRecord).where(_LegacyModelTypeRecord.id == record_id))
|
||||
|
||||
assert str(exc.value) == f"'{legacy_value}' is not a valid ModelType"
|
||||
|
||||
@@ -860,20 +860,19 @@ class TestAppService:
|
||||
mock_current_user.id = account.id
|
||||
mock_current_user.current_tenant_id = account.current_tenant_id
|
||||
|
||||
with patch("services.app_service.current_user", mock_current_user):
|
||||
with pytest.raises(ValueError):
|
||||
app_service.update_app(
|
||||
app,
|
||||
{
|
||||
"name": "Updated App Name",
|
||||
"description": "Updated app description",
|
||||
"icon_type": "",
|
||||
"icon": "🔄",
|
||||
"icon_background": "#FF8C42",
|
||||
"use_icon_as_answer_icon": True,
|
||||
},
|
||||
session=db_session_with_containers,
|
||||
)
|
||||
with patch("services.app_service.current_user", mock_current_user), pytest.raises(ValueError):
|
||||
app_service.update_app(
|
||||
app,
|
||||
{
|
||||
"name": "Updated App Name",
|
||||
"description": "Updated app description",
|
||||
"icon_type": "",
|
||||
"icon": "🔄",
|
||||
"icon_background": "#FF8C42",
|
||||
"use_icon_as_answer_icon": True,
|
||||
},
|
||||
session=db_session_with_containers,
|
||||
)
|
||||
|
||||
def test_update_app_name_success(self, db_session_with_containers: Session, mock_external_service_dependencies):
|
||||
"""
|
||||
|
||||
@@ -73,8 +73,10 @@ class TestAttachmentService:
|
||||
def test_should_raise_not_found_when_file_missing(self, db_session_with_containers: Session):
|
||||
service = AttachmentService(session_factory=sessionmaker(bind=db.engine))
|
||||
|
||||
with patch.object(attachment_service_module.storage, "load_once") as mock_load:
|
||||
with pytest.raises(NotFound, match="File not found"):
|
||||
service.get_file_base64(str(uuid4()))
|
||||
with (
|
||||
patch.object(attachment_service_module.storage, "load_once") as mock_load,
|
||||
pytest.raises(NotFound, match="File not found"),
|
||||
):
|
||||
service.get_file_base64(str(uuid4()))
|
||||
|
||||
mock_load.assert_not_called()
|
||||
|
||||
@@ -1168,14 +1168,16 @@ class TestConversationServiceExport:
|
||||
conversation_id = conversation.id
|
||||
|
||||
# Act — force an error during the soft-delete commit to exercise rollback
|
||||
with patch.object(db_session_with_containers, "commit", side_effect=Exception("DB error")):
|
||||
with pytest.raises(Exception, match="DB error"):
|
||||
ConversationService.delete(
|
||||
app_model=app_model,
|
||||
conversation_id=conversation_id,
|
||||
user=user,
|
||||
session=db_session_with_containers,
|
||||
)
|
||||
with (
|
||||
patch.object(db_session_with_containers, "commit", side_effect=Exception("DB error")),
|
||||
pytest.raises(Exception, match="DB error"),
|
||||
):
|
||||
ConversationService.delete(
|
||||
app_model=app_model,
|
||||
conversation_id=conversation_id,
|
||||
user=user,
|
||||
session=db_session_with_containers,
|
||||
)
|
||||
|
||||
# Assert — related-data deletion is not scheduled.
|
||||
mock_delete_task.delay.assert_not_called()
|
||||
|
||||
@@ -53,10 +53,12 @@ class TestDatasetServiceCreateRagPipelineDataset:
|
||||
tenant, _ = self._create_tenant_and_account(db_session_with_containers)
|
||||
|
||||
mock_user = Mock(id=None)
|
||||
with patch("services.dataset_service.current_user", mock_user):
|
||||
with pytest.raises(ValueError, match="Current user or current user id not found"):
|
||||
DatasetService.create_empty_rag_pipeline_dataset(
|
||||
tenant_id=tenant.id,
|
||||
rag_pipeline_dataset_create_entity=self._build_entity(),
|
||||
session=db_session_with_containers,
|
||||
)
|
||||
with (
|
||||
patch("services.dataset_service.current_user", mock_user),
|
||||
pytest.raises(ValueError, match="Current user or current user id not found"),
|
||||
):
|
||||
DatasetService.create_empty_rag_pipeline_dataset(
|
||||
tenant_id=tenant.id,
|
||||
rag_pipeline_dataset_create_entity=self._build_entity(),
|
||||
session=db_session_with_containers,
|
||||
)
|
||||
|
||||
@@ -286,9 +286,11 @@ def test_get_upload_file_for_upload_file_document_raises_when_file_service_retur
|
||||
data_source_info={"upload_file_id": "missing-file"},
|
||||
)
|
||||
|
||||
with patch("services.dataset_service.FileService.get_upload_files_by_ids", return_value={}):
|
||||
with pytest.raises(NotFound, match="Uploaded file not found"):
|
||||
DocumentService._get_upload_file_for_upload_file_document(document, session=db_session_with_containers)
|
||||
with (
|
||||
patch("services.dataset_service.FileService.get_upload_files_by_ids", return_value={}),
|
||||
pytest.raises(NotFound, match="Uploaded file not found"),
|
||||
):
|
||||
DocumentService._get_upload_file_for_upload_file_document(document, session=db_session_with_containers)
|
||||
|
||||
|
||||
def test_get_upload_file_for_upload_file_document_returns_upload_file(db_session_with_containers: Session):
|
||||
@@ -409,15 +411,14 @@ def test_get_upload_files_by_document_id_for_zip_download_returns_document_keyed
|
||||
def test_prepare_document_batch_download_zip_raises_not_found_for_missing_dataset(
|
||||
current_user_mock, flask_app_with_containers, db_session_with_containers: Session
|
||||
):
|
||||
with flask_app_with_containers.app_context():
|
||||
with pytest.raises(NotFound, match="Dataset not found"):
|
||||
DocumentService.prepare_document_batch_download_zip(
|
||||
dataset_id=str(uuid4()),
|
||||
document_ids=[str(uuid4())],
|
||||
tenant_id=current_user_mock.current_tenant_id,
|
||||
current_user=current_user_mock,
|
||||
session=db_session_with_containers,
|
||||
)
|
||||
with flask_app_with_containers.app_context(), pytest.raises(NotFound, match="Dataset not found"):
|
||||
DocumentService.prepare_document_batch_download_zip(
|
||||
dataset_id=str(uuid4()),
|
||||
document_ids=[str(uuid4())],
|
||||
tenant_id=current_user_mock.current_tenant_id,
|
||||
current_user=current_user_mock,
|
||||
session=db_session_with_containers,
|
||||
)
|
||||
|
||||
|
||||
def test_prepare_document_batch_download_zip_translates_permission_error_to_forbidden(
|
||||
@@ -430,18 +431,20 @@ def test_prepare_document_batch_download_zip_translates_permission_error_to_forb
|
||||
created_by=current_user_mock.id,
|
||||
)
|
||||
|
||||
with patch(
|
||||
"services.dataset_service.DatasetService.check_dataset_permission",
|
||||
side_effect=NoPermissionError("denied"),
|
||||
with (
|
||||
patch(
|
||||
"services.dataset_service.DatasetService.check_dataset_permission",
|
||||
side_effect=NoPermissionError("denied"),
|
||||
),
|
||||
pytest.raises(Forbidden, match="denied"),
|
||||
):
|
||||
with pytest.raises(Forbidden, match="denied"):
|
||||
DocumentService.prepare_document_batch_download_zip(
|
||||
dataset_id=dataset.id,
|
||||
document_ids=[],
|
||||
tenant_id=current_user_mock.current_tenant_id,
|
||||
current_user=current_user_mock,
|
||||
session=db_session_with_containers,
|
||||
)
|
||||
DocumentService.prepare_document_batch_download_zip(
|
||||
dataset_id=dataset.id,
|
||||
document_ids=[],
|
||||
tenant_id=current_user_mock.current_tenant_id,
|
||||
current_user=current_user_mock,
|
||||
session=db_session_with_containers,
|
||||
)
|
||||
|
||||
|
||||
def test_prepare_document_batch_download_zip_returns_upload_files_in_requested_order(
|
||||
|
||||
@@ -480,11 +480,13 @@ class TestDatasetCollectionBindingServiceIntegration:
|
||||
def test_get_dataset_collection_binding_by_id_and_type_raises_when_missing(
|
||||
self, flask_app_with_containers: Flask, db_session_with_containers: Session
|
||||
):
|
||||
with flask_app_with_containers.app_context():
|
||||
with pytest.raises(ValueError, match="Dataset collection binding not found"):
|
||||
DatasetCollectionBindingService.get_dataset_collection_binding_by_id_and_type(
|
||||
str(uuid4()), session=db_session_with_containers
|
||||
)
|
||||
with (
|
||||
flask_app_with_containers.app_context(),
|
||||
pytest.raises(ValueError, match="Dataset collection binding not found"),
|
||||
):
|
||||
DatasetCollectionBindingService.get_dataset_collection_binding_by_id_and_type(
|
||||
str(uuid4()), session=db_session_with_containers
|
||||
)
|
||||
|
||||
def test_get_dataset_collection_binding_by_id_and_type_returns_binding(self, db_session_with_containers: Session):
|
||||
binding = DatasetPermissionIntegrationFactory.create_collection_binding(
|
||||
@@ -593,15 +595,17 @@ class TestDatasetPermissionServiceIntegration:
|
||||
user = SimpleNamespace(is_dataset_editor=True, is_dataset_operator=True)
|
||||
dataset = SimpleNamespace(id="dataset-1", permission=DatasetPermissionEnum.PARTIAL_TEAM)
|
||||
|
||||
with patch.object(DatasetPermissionService, "get_dataset_partial_member_list", return_value=["user-1"]):
|
||||
with pytest.raises(ValueError, match="cannot change the dataset permissions"):
|
||||
DatasetPermissionService.check_permission(
|
||||
user,
|
||||
dataset,
|
||||
DatasetPermissionEnum.PARTIAL_TEAM,
|
||||
[{"user_id": "user-2"}],
|
||||
session=db_session_with_containers,
|
||||
)
|
||||
with (
|
||||
patch.object(DatasetPermissionService, "get_dataset_partial_member_list", return_value=["user-1"]),
|
||||
pytest.raises(ValueError, match="cannot change the dataset permissions"),
|
||||
):
|
||||
DatasetPermissionService.check_permission(
|
||||
user,
|
||||
dataset,
|
||||
DatasetPermissionEnum.PARTIAL_TEAM,
|
||||
[{"user_id": "user-2"}],
|
||||
session=db_session_with_containers,
|
||||
)
|
||||
|
||||
def test_check_permission_allows_dataset_operator_when_member_list_is_unchanged(
|
||||
self, db_session_with_containers: Session
|
||||
|
||||
@@ -59,12 +59,11 @@ def test_build_upload_files_zip_tempfile_sanitizes_and_dedupes_names(monkeypatch
|
||||
|
||||
monkeypatch.setattr(file_service_module.storage, "load", _load)
|
||||
|
||||
with FileService.build_upload_files_zip_tempfile(upload_files=upload_files) as tmp:
|
||||
with ZipFile(tmp, mode="r") as zf:
|
||||
assert zf.namelist() == ["b.txt", "b (1).txt", "b (2).txt"]
|
||||
assert zf.read("b.txt") == b"one"
|
||||
assert zf.read("b (1).txt") == b"two"
|
||||
assert zf.read("b (2).txt") == b"three"
|
||||
with FileService.build_upload_files_zip_tempfile(upload_files=upload_files) as tmp, ZipFile(tmp, mode="r") as zf:
|
||||
assert zf.namelist() == ["b.txt", "b (1).txt", "b (2).txt"]
|
||||
assert zf.read("b.txt") == b"one"
|
||||
assert zf.read("b (1).txt") == b"two"
|
||||
assert zf.read("b (2).txt") == b"three"
|
||||
|
||||
|
||||
def test_get_upload_files_by_ids_returns_empty_when_no_ids(db_session_with_containers: Session) -> None:
|
||||
|
||||
@@ -311,8 +311,10 @@ class TestMetadataPartialUpdate:
|
||||
)
|
||||
metadata_args = MetadataOperationData(operation_data=[operation])
|
||||
|
||||
with patch.object(db_session_with_containers, "commit", side_effect=RuntimeError("database connection lost")):
|
||||
with pytest.raises(RuntimeError, match="database connection lost"):
|
||||
MetadataService.update_documents_metadata(
|
||||
dataset, metadata_args, current_account, session=db_session_with_containers
|
||||
)
|
||||
with (
|
||||
patch.object(db_session_with_containers, "commit", side_effect=RuntimeError("database connection lost")),
|
||||
pytest.raises(RuntimeError, match="database connection lost"),
|
||||
):
|
||||
MetadataService.update_documents_metadata(
|
||||
dataset, metadata_args, current_account, session=db_session_with_containers
|
||||
)
|
||||
|
||||
@@ -160,6 +160,5 @@ class TestWebhookServiceDatabaseLookup:
|
||||
assert node_config["data"].title == "Test Webhook"
|
||||
|
||||
def test_get_webhook_trigger_and_workflow_not_found(self, flask_app_with_containers: Flask) -> None:
|
||||
with flask_app_with_containers.app_context():
|
||||
with pytest.raises(ValueError, match="Webhook not found"):
|
||||
WebhookService.get_webhook_trigger_and_workflow("nonexistent_webhook")
|
||||
with flask_app_with_containers.app_context(), pytest.raises(ValueError, match="Webhook not found"):
|
||||
WebhookService.get_webhook_trigger_and_workflow("nonexistent_webhook")
|
||||
|
||||
@@ -385,13 +385,13 @@ class TestWebhookServiceTriggerExecutionWithContainers:
|
||||
patch(
|
||||
"services.trigger.webhook_service.AppTriggerService.mark_tenant_triggers_rate_limited"
|
||||
) as mock_mark_rate_limited,
|
||||
pytest.raises(QuotaExceededError),
|
||||
):
|
||||
with pytest.raises(QuotaExceededError):
|
||||
WebhookService.trigger_workflow_execution(
|
||||
webhook_trigger,
|
||||
{"body": {}, "headers": {}, "query_params": {}, "files": {}, "method": "POST"},
|
||||
workflow,
|
||||
)
|
||||
WebhookService.trigger_workflow_execution(
|
||||
webhook_trigger,
|
||||
{"body": {}, "headers": {}, "query_params": {}, "files": {}, "method": "POST"},
|
||||
workflow,
|
||||
)
|
||||
|
||||
mock_mark_rate_limited.assert_called_once_with(tenant.id)
|
||||
|
||||
@@ -413,16 +413,18 @@ class TestWebhookServiceTriggerExecutionWithContainers:
|
||||
)
|
||||
caplog.set_level(logging.ERROR, logger="services.trigger.webhook_service")
|
||||
|
||||
with patch(
|
||||
"services.trigger.webhook_service.EndUserService.get_or_create_end_user_by_type",
|
||||
side_effect=RuntimeError("boom"),
|
||||
with (
|
||||
patch(
|
||||
"services.trigger.webhook_service.EndUserService.get_or_create_end_user_by_type",
|
||||
side_effect=RuntimeError("boom"),
|
||||
),
|
||||
pytest.raises(RuntimeError, match="boom"),
|
||||
):
|
||||
with pytest.raises(RuntimeError, match="boom"):
|
||||
WebhookService.trigger_workflow_execution(
|
||||
webhook_trigger,
|
||||
{"body": {}, "headers": {}, "query_params": {}, "files": {}, "method": "POST"},
|
||||
workflow,
|
||||
)
|
||||
WebhookService.trigger_workflow_execution(
|
||||
webhook_trigger,
|
||||
{"body": {}, "headers": {}, "query_params": {}, "files": {}, "method": "POST"},
|
||||
workflow,
|
||||
)
|
||||
|
||||
assert caplog.messages.count(f"Failed to trigger workflow for webhook {webhook_trigger.webhook_id}") == 1
|
||||
|
||||
@@ -456,9 +458,11 @@ class TestWebhookServiceRelationshipSyncWithContainers:
|
||||
lock = MagicMock()
|
||||
lock.acquire.return_value = False
|
||||
|
||||
with patch("services.trigger.webhook_service.redis_client.lock", return_value=lock):
|
||||
with pytest.raises(RuntimeError, match="Failed to acquire lock"):
|
||||
WebhookService.sync_webhook_relationships(app, workflow)
|
||||
with (
|
||||
patch("services.trigger.webhook_service.redis_client.lock", return_value=lock),
|
||||
pytest.raises(RuntimeError, match="Failed to acquire lock"),
|
||||
):
|
||||
WebhookService.sync_webhook_relationships(app, workflow)
|
||||
|
||||
def test_sync_webhook_relationships_creates_missing_records_and_deletes_stale_records(
|
||||
self, db_session_with_containers: Session, flask_app_with_containers: Flask
|
||||
|
||||
@@ -553,9 +553,11 @@ class TestWorkspaceService:
|
||||
db_session_with_containers.commit()
|
||||
|
||||
# No TenantAccountJoin created
|
||||
with patch("services.workspace_service.current_user", account):
|
||||
with pytest.raises(AssertionError, match="TenantAccountJoin not found"):
|
||||
WorkspaceService.get_tenant_info(tenant, db_session_with_containers)
|
||||
with (
|
||||
patch("services.workspace_service.current_user", account),
|
||||
pytest.raises(AssertionError, match="TenantAccountJoin not found"),
|
||||
):
|
||||
WorkspaceService.get_tenant_info(tenant, db_session_with_containers)
|
||||
|
||||
def test_get_tenant_info_should_set_replace_webapp_logo_to_none_when_flag_absent(
|
||||
self, db_session_with_containers: Session, mock_external_service_dependencies
|
||||
|
||||
@@ -30,14 +30,16 @@ class TestToolTransformService:
|
||||
@pytest.fixture
|
||||
def mock_external_service_dependencies(self):
|
||||
"""Mock setup for external service dependencies."""
|
||||
with patch("services.tools.tools_transform_service.dify_config") as mock_dify_config:
|
||||
with patch("core.plugin.plugin_service.dify_config", new=mock_dify_config):
|
||||
# Setup default mock returns
|
||||
mock_dify_config.CONSOLE_API_URL = "https://console.example.com"
|
||||
with (
|
||||
patch("services.tools.tools_transform_service.dify_config") as mock_dify_config,
|
||||
patch("core.plugin.plugin_service.dify_config", new=mock_dify_config),
|
||||
):
|
||||
# Setup default mock returns
|
||||
mock_dify_config.CONSOLE_API_URL = "https://console.example.com"
|
||||
|
||||
yield {
|
||||
"dify_config": mock_dify_config,
|
||||
}
|
||||
yield {
|
||||
"dify_config": mock_dify_config,
|
||||
}
|
||||
|
||||
def test_get_plugin_icon_url_success(self, db_session_with_containers: Session, mock_external_service_dependencies):
|
||||
"""
|
||||
|
||||
@@ -798,9 +798,8 @@ class TestRagPipelineRunTasks:
|
||||
}
|
||||
|
||||
# Act & Assert: Execute the single task with non-existent entities (should raise ValueError)
|
||||
with flask_app_with_containers.app_context():
|
||||
with pytest.raises(ValueError, match="Account .* not found"):
|
||||
run_single_rag_pipeline_task(invalid_entity_data, flask_app_with_containers)
|
||||
with flask_app_with_containers.app_context(), pytest.raises(ValueError, match="Account .* not found"):
|
||||
run_single_rag_pipeline_task(invalid_entity_data, flask_app_with_containers)
|
||||
|
||||
# Assert: Pipeline generator should not be called
|
||||
mock_pipeline_generator.assert_not_called()
|
||||
@@ -843,9 +842,8 @@ class TestRagPipelineRunTasks:
|
||||
}
|
||||
|
||||
# Act & Assert: Execute the single task with non-existent entities (should raise ValueError)
|
||||
with flask_app_with_containers.app_context():
|
||||
with pytest.raises(ValueError, match="Account .* not found"):
|
||||
run_single_rag_pipeline_task(entity_data, flask_app_with_containers)
|
||||
with flask_app_with_containers.app_context(), pytest.raises(ValueError, match="Account .* not found"):
|
||||
run_single_rag_pipeline_task(entity_data, flask_app_with_containers)
|
||||
|
||||
# Assert: Pipeline generator should not be called
|
||||
mock_pipeline_generator.assert_not_called()
|
||||
|
||||
@@ -217,9 +217,8 @@ class TestDeleteDraftVariableOffloadData:
|
||||
|
||||
mock_storage.delete.side_effect = [Exception("Storage error"), None]
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
with session_factory.create_session() as session, session.begin():
|
||||
result = _delete_draft_variable_offload_data(session, file_ids)
|
||||
with caplog.at_level(logging.ERROR), session_factory.create_session() as session, session.begin():
|
||||
result = _delete_draft_variable_offload_data(session, file_ids)
|
||||
|
||||
assert result == 1
|
||||
assert f"Failed to delete storage object {storage_keys[0]}" in caplog.text
|
||||
|
||||
@@ -359,9 +359,8 @@ def test_query_params_from_request_raises_for_malformed_ints_by_default():
|
||||
from controllers.common.schema import query_params_from_request
|
||||
|
||||
app = Flask(__name__)
|
||||
with app.test_request_context("/?page=bad&limit="):
|
||||
with pytest.raises(ValueError):
|
||||
query_params_from_request(HelperQueryModel, list_fields=("status",))
|
||||
with app.test_request_context("/?page=bad&limit="), pytest.raises(ValueError):
|
||||
query_params_from_request(HelperQueryModel, list_fields=("status",))
|
||||
|
||||
|
||||
def test_query_params_from_request_can_use_model_default_for_malformed_defaulted_ints():
|
||||
|
||||
@@ -1942,23 +1942,25 @@ def test_agent_chat_helper_rejects_foreign_debug_conversation_before_generation(
|
||||
"_resolve_current_user_agent_debug_conversation_id",
|
||||
resolve_debug_conversation,
|
||||
)
|
||||
with app.test_request_context(
|
||||
json={
|
||||
"inputs": {},
|
||||
"query": "hello",
|
||||
"response_mode": "streaming",
|
||||
"conversation_id": "00000000-0000-0000-0000-000000000001",
|
||||
**payload_extra,
|
||||
}
|
||||
with (
|
||||
app.test_request_context(
|
||||
json={
|
||||
"inputs": {},
|
||||
"query": "hello",
|
||||
"response_mode": "streaming",
|
||||
"conversation_id": "00000000-0000-0000-0000-000000000001",
|
||||
**payload_extra,
|
||||
}
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
completion_controller._create_chat_message(
|
||||
current_tenant_id="tenant-1",
|
||||
current_user=_account(account_id=account_id),
|
||||
app_model=app_model,
|
||||
agent_id="agent-1",
|
||||
session=unbound_session,
|
||||
)
|
||||
completion_controller._create_chat_message(
|
||||
current_tenant_id="tenant-1",
|
||||
current_user=_account(account_id=account_id),
|
||||
app_model=app_model,
|
||||
agent_id="agent-1",
|
||||
session=unbound_session,
|
||||
)
|
||||
|
||||
resolve_debug_conversation.assert_called_once()
|
||||
resolve_call = resolve_debug_conversation.call_args.kwargs
|
||||
@@ -2098,11 +2100,10 @@ def test_agent_chat_helper_maps_generation_errors(
|
||||
) -> None:
|
||||
app_model = _app_detail_obj(id="app-1", mode=AppMode.CHAT)
|
||||
monkeypatch.setattr(completion_controller.AppGenerateService, "generate", lambda **_: (_ for _ in ()).throw(error))
|
||||
with app.test_request_context(json={"inputs": {}, "query": "hello"}):
|
||||
with pytest.raises(expected):
|
||||
completion_controller._create_chat_message(
|
||||
current_user=_account(), app_model=app_model, session=unbound_session
|
||||
)
|
||||
with app.test_request_context(json={"inputs": {}, "query": "hello"}), pytest.raises(expected):
|
||||
completion_controller._create_chat_message(
|
||||
current_user=_account(), app_model=app_model, session=unbound_session
|
||||
)
|
||||
|
||||
|
||||
def test_agent_chat_message_routes_resolve_app_from_agent_id(
|
||||
@@ -2279,13 +2280,15 @@ def test_list_agent_chat_messages_rejects_foreign_conversation(
|
||||
"get_conversation",
|
||||
lambda **kwargs: (_ for _ in ()).throw(message_controller.ConversationNotExistsError()),
|
||||
)
|
||||
with app.test_request_context(f"/console/api/agent/agent-1/chat-messages?conversation_id={conversation_id}"):
|
||||
with pytest.raises(NotFound):
|
||||
message_controller._list_chat_messages(
|
||||
session=unbound_session,
|
||||
app_model=_app_detail_obj(id="app-1", mode=AppMode.AGENT),
|
||||
current_user=_account(),
|
||||
)
|
||||
with (
|
||||
app.test_request_context(f"/console/api/agent/agent-1/chat-messages?conversation_id={conversation_id}"),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
message_controller._list_chat_messages(
|
||||
session=unbound_session,
|
||||
app_model=_app_detail_obj(id="app-1", mode=AppMode.AGENT),
|
||||
current_user=_account(),
|
||||
)
|
||||
|
||||
|
||||
def test_update_message_feedback_rejects_empty_rating_without_existing_feedback(
|
||||
@@ -2300,13 +2303,15 @@ def test_update_message_feedback_rejects_empty_rating_without_existing_feedback(
|
||||
message_id=message_id,
|
||||
created_at=datetime(2025, 1, 1),
|
||||
)
|
||||
with app.test_request_context(json={"message_id": message_id, "rating": None}):
|
||||
with pytest.raises(ValueError, match="rating cannot be None"):
|
||||
message_controller._update_message_feedback(
|
||||
session=sqlite_session,
|
||||
current_user=_account(),
|
||||
app_model=_app_detail_obj(id=app_id),
|
||||
)
|
||||
with (
|
||||
app.test_request_context(json={"message_id": message_id, "rating": None}),
|
||||
pytest.raises(ValueError, match="rating cannot be None"),
|
||||
):
|
||||
message_controller._update_message_feedback(
|
||||
session=sqlite_session,
|
||||
current_user=_account(),
|
||||
app_model=_app_detail_obj(id=app_id),
|
||||
)
|
||||
|
||||
assert message.admin_feedback_with_session(session=sqlite_session) is None
|
||||
|
||||
|
||||
@@ -78,22 +78,22 @@ def test_resolve_bound_agent_uses_injected_session(unbound_session: Session):
|
||||
|
||||
def test_manifest_by_agent_resolves_build_draft_version(unbound_session: Session):
|
||||
raw = _raw(AgentConfigManifestByAgentApi.get)
|
||||
with app.test_request_context("/?draft_type=debug_build"):
|
||||
with (
|
||||
patch(f"{_MOD}.resolve_agent_runtime_app_model", return_value=_APP),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.load_agent_app_build_draft.return_value = {"draft": {"id": "build-draft-1"}}
|
||||
config_service.return_value.manifest.return_value = {
|
||||
"agent_id": "agent-1",
|
||||
"config_version": {"id": "build-draft-1", "kind": "build_draft", "writable": True},
|
||||
"skills": {"items": []},
|
||||
"files": {"items": []},
|
||||
"env_keys": [],
|
||||
"note": "",
|
||||
}
|
||||
body = raw(AgentConfigManifestByAgentApi(), unbound_session, "tenant-1", _USER, "agent-1")
|
||||
with (
|
||||
app.test_request_context("/?draft_type=debug_build"),
|
||||
patch(f"{_MOD}.resolve_agent_runtime_app_model", return_value=_APP),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.load_agent_app_build_draft.return_value = {"draft": {"id": "build-draft-1"}}
|
||||
config_service.return_value.manifest.return_value = {
|
||||
"agent_id": "agent-1",
|
||||
"config_version": {"id": "build-draft-1", "kind": "build_draft", "writable": True},
|
||||
"skills": {"items": []},
|
||||
"files": {"items": []},
|
||||
"env_keys": [],
|
||||
"note": "",
|
||||
}
|
||||
body = raw(AgentConfigManifestByAgentApi(), unbound_session, "tenant-1", _USER, "agent-1")
|
||||
assert body["config_version"]["kind"] == "build_draft"
|
||||
assert config_service.return_value.manifest.call_args.kwargs["config_version_id"] == "build-draft-1"
|
||||
assert config_service.return_value.manifest.call_args.kwargs["config_version_kind"].value == "build_draft"
|
||||
@@ -101,19 +101,22 @@ def test_manifest_by_agent_resolves_build_draft_version(unbound_session: Session
|
||||
|
||||
def test_manifest_resolves_workflow_node_agent_and_normal_draft(unbound_session: Session):
|
||||
raw = _raw(AgentConfigManifestApi.get)
|
||||
with app.test_request_context("/?node_id=node-1"):
|
||||
with patch(f"{_MOD}.AgentComposerService") as composer, patch(f"{_MOD}.AgentConfigService") as config_service:
|
||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9"
|
||||
composer.load_agent_composer.return_value = {"draft": {"id": "draft-1"}}
|
||||
config_service.return_value.manifest.return_value = {
|
||||
"agent_id": "wf-agent-9",
|
||||
"config_version": {"id": "draft-1", "kind": "draft", "writable": True},
|
||||
"skills": {"items": []},
|
||||
"files": {"items": []},
|
||||
"env_keys": [],
|
||||
"note": "",
|
||||
}
|
||||
body = raw(AgentConfigManifestApi(), unbound_session, _USER, _APP)
|
||||
with (
|
||||
app.test_request_context("/?node_id=node-1"),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9"
|
||||
composer.load_agent_composer.return_value = {"draft": {"id": "draft-1"}}
|
||||
config_service.return_value.manifest.return_value = {
|
||||
"agent_id": "wf-agent-9",
|
||||
"config_version": {"id": "draft-1", "kind": "draft", "writable": True},
|
||||
"skills": {"items": []},
|
||||
"files": {"items": []},
|
||||
"env_keys": [],
|
||||
"note": "",
|
||||
}
|
||||
body = raw(AgentConfigManifestApi(), unbound_session, _USER, _APP)
|
||||
assert body["agent_id"] == "wf-agent-9"
|
||||
assert composer.resolve_workflow_node_agent_id.call_args.kwargs["node_id"] == "node-1"
|
||||
assert config_service.return_value.manifest.call_args.kwargs["config_version_kind"].value == "draft"
|
||||
@@ -140,36 +143,36 @@ def test_normal_draft_resolution_commits_created_draft_before_service_session(sq
|
||||
|
||||
def test_skill_inspect_by_agent_returns_strict_json_response(unbound_session: Session):
|
||||
raw = _raw(AgentConfigSkillInspectByAgentApi.get)
|
||||
with app.test_request_context("/"):
|
||||
with (
|
||||
patch(f"{_MOD}.resolve_agent_runtime_app_model", return_value=_APP),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.load_agent_composer.return_value = {"draft": {"id": "draft-1"}}
|
||||
config_service.return_value.inspect_skill.return_value = {
|
||||
"id": "pdf-toolkit",
|
||||
"name": "pdf-toolkit",
|
||||
"description": "Work with PDFs.",
|
||||
"size": 42,
|
||||
"mime_type": "application/zip",
|
||||
"hash": "sha256:abc",
|
||||
"source": "config_skill_zip",
|
||||
"files": [
|
||||
{"path": "SKILL.md", "name": "SKILL.md", "type": "file", "previewable": True, "downloadable": True}
|
||||
],
|
||||
"skill_md": {
|
||||
"path": "SKILL.md",
|
||||
"size": 24,
|
||||
"truncated": False,
|
||||
"binary": False,
|
||||
"text": "# PDF Toolkit\nUse it.\n",
|
||||
},
|
||||
"warnings": [],
|
||||
}
|
||||
response = raw(
|
||||
AgentConfigSkillInspectByAgentApi(), unbound_session, "tenant-1", _USER, "agent-1", "pdf-toolkit"
|
||||
)
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch(f"{_MOD}.resolve_agent_runtime_app_model", return_value=_APP),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.load_agent_composer.return_value = {"draft": {"id": "draft-1"}}
|
||||
config_service.return_value.inspect_skill.return_value = {
|
||||
"id": "pdf-toolkit",
|
||||
"name": "pdf-toolkit",
|
||||
"description": "Work with PDFs.",
|
||||
"size": 42,
|
||||
"mime_type": "application/zip",
|
||||
"hash": "sha256:abc",
|
||||
"source": "config_skill_zip",
|
||||
"files": [
|
||||
{"path": "SKILL.md", "name": "SKILL.md", "type": "file", "previewable": True, "downloadable": True}
|
||||
],
|
||||
"skill_md": {
|
||||
"path": "SKILL.md",
|
||||
"size": 24,
|
||||
"truncated": False,
|
||||
"binary": False,
|
||||
"text": "# PDF Toolkit\nUse it.\n",
|
||||
},
|
||||
"warnings": [],
|
||||
}
|
||||
response = raw(
|
||||
AgentConfigSkillInspectByAgentApi(), unbound_session, "tenant-1", _USER, "agent-1", "pdf-toolkit"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.get_json()["name"] == "pdf-toolkit"
|
||||
assert b"PDF Toolkit" in response.get_data()
|
||||
@@ -177,50 +180,56 @@ def test_skill_inspect_by_agent_returns_strict_json_response(unbound_session: Se
|
||||
|
||||
def test_file_preview_api_passes_through_and_maps_errors(unbound_session: Session):
|
||||
raw = _raw(AgentConfigFilePreviewApi.get)
|
||||
with app.test_request_context("/?node_id=node-1"):
|
||||
with patch(f"{_MOD}.AgentComposerService") as composer, patch(f"{_MOD}.AgentConfigService") as config_service:
|
||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9"
|
||||
composer.load_agent_composer.return_value = {"draft": {"id": "draft-1"}}
|
||||
config_service.return_value.preview_file.return_value = {
|
||||
"name": "sample.txt",
|
||||
"size": 5,
|
||||
"truncated": False,
|
||||
"binary": False,
|
||||
"text": "hello",
|
||||
}
|
||||
body = raw(AgentConfigFilePreviewApi(), unbound_session, _USER, _APP, "sample.txt")
|
||||
with (
|
||||
app.test_request_context("/?node_id=node-1"),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9"
|
||||
composer.load_agent_composer.return_value = {"draft": {"id": "draft-1"}}
|
||||
config_service.return_value.preview_file.return_value = {
|
||||
"name": "sample.txt",
|
||||
"size": 5,
|
||||
"truncated": False,
|
||||
"binary": False,
|
||||
"text": "hello",
|
||||
}
|
||||
body = raw(AgentConfigFilePreviewApi(), unbound_session, _USER, _APP, "sample.txt")
|
||||
assert body["text"] == "hello"
|
||||
with app.test_request_context("/?node_id=node-1"):
|
||||
with patch(f"{_MOD}.AgentComposerService") as composer, patch(f"{_MOD}.AgentConfigService") as config_service:
|
||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9"
|
||||
composer.load_agent_composer.return_value = {"draft": {"id": "draft-1"}}
|
||||
config_service.return_value.preview_file.side_effect = AgentConfigServiceError(
|
||||
"config_file_not_found", "missing", status_code=404
|
||||
)
|
||||
body, status = raw(AgentConfigFilePreviewApi(), unbound_session, _USER, _APP, "missing.txt")
|
||||
with (
|
||||
app.test_request_context("/?node_id=node-1"),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9"
|
||||
composer.load_agent_composer.return_value = {"draft": {"id": "draft-1"}}
|
||||
config_service.return_value.preview_file.side_effect = AgentConfigServiceError(
|
||||
"config_file_not_found", "missing", status_code=404
|
||||
)
|
||||
body, status = raw(AgentConfigFilePreviewApi(), unbound_session, _USER, _APP, "missing.txt")
|
||||
assert status == 404
|
||||
assert body["code"] == "config_file_not_found"
|
||||
|
||||
|
||||
def test_skill_upload_by_agent_delegates_after_version_resolution(unbound_session: Session):
|
||||
raw = _raw(AgentConfigSkillUploadByAgentApi.post)
|
||||
with app.test_request_context("/?draft_type=debug_build"):
|
||||
with (
|
||||
patch(f"{_MOD}.resolve_agent_runtime_app_model", return_value=_APP),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(
|
||||
f"{_MOD}._upload_skill_for_target",
|
||||
return_value=(
|
||||
{
|
||||
"skill": {"id": "alpha", "name": "alpha", "file_id": "tool-file-1", "description": "Alpha"},
|
||||
"config_version": {"id": "build-draft-1", "kind": "build_draft", "writable": True},
|
||||
},
|
||||
201,
|
||||
),
|
||||
) as upload_skill,
|
||||
):
|
||||
composer.load_agent_app_build_draft.return_value = {"draft": {"id": "build-draft-1"}}
|
||||
body, status = raw(AgentConfigSkillUploadByAgentApi(), unbound_session, "tenant-1", _USER, "agent-1")
|
||||
with (
|
||||
app.test_request_context("/?draft_type=debug_build"),
|
||||
patch(f"{_MOD}.resolve_agent_runtime_app_model", return_value=_APP),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(
|
||||
f"{_MOD}._upload_skill_for_target",
|
||||
return_value=(
|
||||
{
|
||||
"skill": {"id": "alpha", "name": "alpha", "file_id": "tool-file-1", "description": "Alpha"},
|
||||
"config_version": {"id": "build-draft-1", "kind": "build_draft", "writable": True},
|
||||
},
|
||||
201,
|
||||
),
|
||||
) as upload_skill,
|
||||
):
|
||||
composer.load_agent_app_build_draft.return_value = {"draft": {"id": "build-draft-1"}}
|
||||
body, status = raw(AgentConfigSkillUploadByAgentApi(), unbound_session, "tenant-1", _USER, "agent-1")
|
||||
assert status == 201
|
||||
assert body["skill"]["name"] == "alpha"
|
||||
assert upload_skill.call_args.kwargs["version_id"] == "build-draft-1"
|
||||
@@ -229,25 +238,25 @@ def test_skill_upload_by_agent_delegates_after_version_resolution(unbound_sessio
|
||||
|
||||
def test_file_upload_by_agent_delegates_to_service_owned_upload_lookup(unbound_session: Session):
|
||||
raw = _raw(AgentConfigFilesByAgentApi.post)
|
||||
with app.test_request_context("/?draft_type=debug_build", json={"upload_file_id": "upload-1"}):
|
||||
with (
|
||||
patch(f"{_MOD}.resolve_agent_runtime_app_model", return_value=_APP),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.load_agent_app_build_draft.return_value = {"draft": {"id": "build-draft-1"}}
|
||||
config_service.return_value.push_file_for_console.return_value = {
|
||||
"file": {"id": "guide.txt", "name": "guide.txt", "file_id": "upload-1"},
|
||||
"config_version": {"id": "build-draft-1", "kind": "build_draft", "writable": True},
|
||||
}
|
||||
body, status = raw(
|
||||
AgentConfigFilesByAgentApi(),
|
||||
inspector.AgentConfigFileUploadPayload(upload_file_id="upload-1"),
|
||||
unbound_session,
|
||||
"tenant-1",
|
||||
_USER,
|
||||
"agent-1",
|
||||
)
|
||||
with (
|
||||
app.test_request_context("/?draft_type=debug_build", json={"upload_file_id": "upload-1"}),
|
||||
patch(f"{_MOD}.resolve_agent_runtime_app_model", return_value=_APP),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.load_agent_app_build_draft.return_value = {"draft": {"id": "build-draft-1"}}
|
||||
config_service.return_value.push_file_for_console.return_value = {
|
||||
"file": {"id": "guide.txt", "name": "guide.txt", "file_id": "upload-1"},
|
||||
"config_version": {"id": "build-draft-1", "kind": "build_draft", "writable": True},
|
||||
}
|
||||
body, status = raw(
|
||||
AgentConfigFilesByAgentApi(),
|
||||
inspector.AgentConfigFileUploadPayload(upload_file_id="upload-1"),
|
||||
unbound_session,
|
||||
"tenant-1",
|
||||
_USER,
|
||||
"agent-1",
|
||||
)
|
||||
assert status == 201
|
||||
assert body["file"]["name"] == "guide.txt"
|
||||
assert config_service.return_value.push_file_for_console.call_args.kwargs["upload_file_id"] == "upload-1"
|
||||
@@ -259,16 +268,19 @@ def test_file_upload_by_agent_delegates_to_service_owned_upload_lookup(unbound_s
|
||||
|
||||
def test_skill_list_api_uses_config_list_shape(unbound_session: Session) -> None:
|
||||
raw = _raw(AgentConfigSkillsApi.get)
|
||||
with app.test_request_context("/?node_id=node-1"):
|
||||
with patch(f"{_MOD}.AgentComposerService") as composer, patch(f"{_MOD}.AgentConfigService") as config_service:
|
||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9"
|
||||
composer.load_agent_composer.return_value = {"draft": {"id": "draft-1"}}
|
||||
config_service.return_value.list_skills.return_value = {
|
||||
"agent_id": "wf-agent-9",
|
||||
"config_version": {"id": "draft-1", "kind": "draft", "writable": True},
|
||||
"items": [{"id": "alpha", "name": "alpha", "file_id": "tool-file-1", "description": "Alpha"}],
|
||||
}
|
||||
body = raw(AgentConfigSkillsApi(), unbound_session, _USER, _APP)
|
||||
with (
|
||||
app.test_request_context("/?node_id=node-1"),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9"
|
||||
composer.load_agent_composer.return_value = {"draft": {"id": "draft-1"}}
|
||||
config_service.return_value.list_skills.return_value = {
|
||||
"agent_id": "wf-agent-9",
|
||||
"config_version": {"id": "draft-1", "kind": "draft", "writable": True},
|
||||
"items": [{"id": "alpha", "name": "alpha", "file_id": "tool-file-1", "description": "Alpha"}],
|
||||
}
|
||||
body = raw(AgentConfigSkillsApi(), unbound_session, _USER, _APP)
|
||||
assert body["items"][0]["name"] == "alpha"
|
||||
assert body["items"][0]["file_id"] == "tool-file-1"
|
||||
assert config_service.return_value.list_skills.call_args.kwargs["agent_id"] == "wf-agent-9"
|
||||
@@ -276,25 +288,28 @@ def test_skill_list_api_uses_config_list_shape(unbound_session: Session) -> None
|
||||
|
||||
def test_file_list_api_uses_config_list_shape(unbound_session: Session) -> None:
|
||||
raw = _raw(AgentConfigFilesApi.get)
|
||||
with app.test_request_context("/?node_id=node-1"):
|
||||
with patch(f"{_MOD}.AgentComposerService") as composer, patch(f"{_MOD}.AgentConfigService") as config_service:
|
||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9"
|
||||
composer.load_agent_composer.return_value = {"draft": {"id": "draft-1"}}
|
||||
config_service.return_value.list_files.return_value = {
|
||||
"agent_id": "wf-agent-9",
|
||||
"config_version": {"id": "draft-1", "kind": "draft", "writable": True},
|
||||
"items": [
|
||||
{
|
||||
"id": "guide.txt",
|
||||
"name": "guide.txt",
|
||||
"file_id": "upload-file-1",
|
||||
"hash": "sha256:file-1",
|
||||
"mime_type": "text/plain",
|
||||
"size": 7,
|
||||
}
|
||||
],
|
||||
}
|
||||
body = raw(AgentConfigFilesApi(), unbound_session, _USER, _APP)
|
||||
with (
|
||||
app.test_request_context("/?node_id=node-1"),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9"
|
||||
composer.load_agent_composer.return_value = {"draft": {"id": "draft-1"}}
|
||||
config_service.return_value.list_files.return_value = {
|
||||
"agent_id": "wf-agent-9",
|
||||
"config_version": {"id": "draft-1", "kind": "draft", "writable": True},
|
||||
"items": [
|
||||
{
|
||||
"id": "guide.txt",
|
||||
"name": "guide.txt",
|
||||
"file_id": "upload-file-1",
|
||||
"hash": "sha256:file-1",
|
||||
"mime_type": "text/plain",
|
||||
"size": 7,
|
||||
}
|
||||
],
|
||||
}
|
||||
body = raw(AgentConfigFilesApi(), unbound_session, _USER, _APP)
|
||||
assert body == {
|
||||
"agent_id": "wf-agent-9",
|
||||
"config_version": {"id": "draft-1", "kind": "draft", "writable": True},
|
||||
@@ -314,42 +329,40 @@ def test_file_list_api_uses_config_list_shape(unbound_session: Session) -> None:
|
||||
|
||||
def test_skill_file_preview_by_agent_reads_path_query(unbound_session: Session) -> None:
|
||||
raw = _raw(AgentConfigSkillFilePreviewByAgentApi.get)
|
||||
with app.test_request_context("/?draft_type=debug_build&path=references/guide.md"):
|
||||
with (
|
||||
patch(f"{_MOD}.resolve_agent_runtime_app_model", return_value=_APP),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.load_agent_app_build_draft.return_value = {"draft": {"id": "build-draft-1"}}
|
||||
config_service.return_value.preview_skill_file.return_value = {
|
||||
"path": "references/guide.md",
|
||||
"size": 11,
|
||||
"truncated": False,
|
||||
"binary": False,
|
||||
"text": "hello world",
|
||||
}
|
||||
body = raw(AgentConfigSkillFilePreviewByAgentApi(), unbound_session, "tenant-1", _USER, "agent-1", "alpha")
|
||||
with (
|
||||
app.test_request_context("/?draft_type=debug_build&path=references/guide.md"),
|
||||
patch(f"{_MOD}.resolve_agent_runtime_app_model", return_value=_APP),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.load_agent_app_build_draft.return_value = {"draft": {"id": "build-draft-1"}}
|
||||
config_service.return_value.preview_skill_file.return_value = {
|
||||
"path": "references/guide.md",
|
||||
"size": 11,
|
||||
"truncated": False,
|
||||
"binary": False,
|
||||
"text": "hello world",
|
||||
}
|
||||
body = raw(AgentConfigSkillFilePreviewByAgentApi(), unbound_session, "tenant-1", _USER, "agent-1", "alpha")
|
||||
assert body["path"] == "references/guide.md"
|
||||
assert config_service.return_value.preview_skill_file.call_args.kwargs["path"] == "references/guide.md"
|
||||
|
||||
|
||||
def test_skill_file_download_by_agent_returns_proxy_url(unbound_session: Session) -> None:
|
||||
raw = _raw(AgentConfigSkillFileDownloadByAgentApi.get)
|
||||
with app.test_request_context("/?draft_type=debug_build&path=references/guide.md"):
|
||||
with (
|
||||
patch(f"{_MOD}.resolve_agent_runtime_app_model", return_value=_APP),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
patch(
|
||||
f"{_MOD}.url_for",
|
||||
return_value="/console/api/agent/agent-1/config/skills/alpha/files/content?path=references%2Fguide.md&draft_type=debug_build",
|
||||
),
|
||||
):
|
||||
composer.load_agent_app_build_draft.return_value = {"draft": {"id": "build-draft-1"}}
|
||||
config_service.return_value.resolve_skill_file_member_path.return_value = "references/guide.md"
|
||||
response = raw(
|
||||
AgentConfigSkillFileDownloadByAgentApi(), unbound_session, "tenant-1", _USER, "agent-1", "alpha"
|
||||
)
|
||||
with (
|
||||
app.test_request_context("/?draft_type=debug_build&path=references/guide.md"),
|
||||
patch(f"{_MOD}.resolve_agent_runtime_app_model", return_value=_APP),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
patch(
|
||||
f"{_MOD}.url_for",
|
||||
return_value="/console/api/agent/agent-1/config/skills/alpha/files/content?path=references%2Fguide.md&draft_type=debug_build",
|
||||
),
|
||||
):
|
||||
composer.load_agent_app_build_draft.return_value = {"draft": {"id": "build-draft-1"}}
|
||||
config_service.return_value.resolve_skill_file_member_path.return_value = "references/guide.md"
|
||||
response = raw(AgentConfigSkillFileDownloadByAgentApi(), unbound_session, "tenant-1", _USER, "agent-1", "alpha")
|
||||
assert response.status_code == 200
|
||||
assert response.get_json()["url"].endswith(
|
||||
"/agent/agent-1/config/skills/alpha/files/content?path=references%2Fguide.md&draft_type=debug_build"
|
||||
@@ -358,38 +371,38 @@ def test_skill_file_download_by_agent_returns_proxy_url(unbound_session: Session
|
||||
|
||||
def test_skill_file_download_by_agent_validates_member_path(unbound_session: Session) -> None:
|
||||
raw = _raw(AgentConfigSkillFileDownloadByAgentApi.get)
|
||||
with app.test_request_context("/?draft_type=debug_build&path=references/missing.md"):
|
||||
with (
|
||||
patch(f"{_MOD}.resolve_agent_runtime_app_model", return_value=_APP),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.load_agent_app_build_draft.return_value = {"draft": {"id": "build-draft-1"}}
|
||||
config_service.return_value.resolve_skill_file_member_path.side_effect = AgentConfigServiceError(
|
||||
"config_skill_file_not_found", "missing", status_code=404
|
||||
)
|
||||
body, status = raw(
|
||||
AgentConfigSkillFileDownloadByAgentApi(), unbound_session, "tenant-1", _USER, "agent-1", "alpha"
|
||||
)
|
||||
with (
|
||||
app.test_request_context("/?draft_type=debug_build&path=references/missing.md"),
|
||||
patch(f"{_MOD}.resolve_agent_runtime_app_model", return_value=_APP),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.load_agent_app_build_draft.return_value = {"draft": {"id": "build-draft-1"}}
|
||||
config_service.return_value.resolve_skill_file_member_path.side_effect = AgentConfigServiceError(
|
||||
"config_skill_file_not_found", "missing", status_code=404
|
||||
)
|
||||
body, status = raw(
|
||||
AgentConfigSkillFileDownloadByAgentApi(), unbound_session, "tenant-1", _USER, "agent-1", "alpha"
|
||||
)
|
||||
assert status == 404
|
||||
assert body["code"] == "config_skill_file_not_found"
|
||||
|
||||
|
||||
def test_skill_file_download_api_forwards_workflow_node_and_draft_type(unbound_session: Session) -> None:
|
||||
raw = _raw(AgentConfigSkillFileDownloadApi.get)
|
||||
with app.test_request_context("/?node_id=node-1&draft_type=debug_build&path=references/guide.md"):
|
||||
with (
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
patch(
|
||||
f"{_MOD}.url_for",
|
||||
return_value="/console/api/apps/app-1/agent/config/skills/alpha/files/content?node_id=node-1&draft_type=debug_build&path=references%2Fguide.md",
|
||||
) as url_for_mock,
|
||||
):
|
||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9"
|
||||
composer.load_agent_app_build_draft.return_value = {"draft": {"id": "build-draft-1"}}
|
||||
config_service.return_value.resolve_skill_file_member_path.return_value = "references/guide.md"
|
||||
response = raw(AgentConfigSkillFileDownloadApi(), unbound_session, _USER, _APP, "alpha")
|
||||
with (
|
||||
app.test_request_context("/?node_id=node-1&draft_type=debug_build&path=references/guide.md"),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
patch(
|
||||
f"{_MOD}.url_for",
|
||||
return_value="/console/api/apps/app-1/agent/config/skills/alpha/files/content?node_id=node-1&draft_type=debug_build&path=references%2Fguide.md",
|
||||
) as url_for_mock,
|
||||
):
|
||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9"
|
||||
composer.load_agent_app_build_draft.return_value = {"draft": {"id": "build-draft-1"}}
|
||||
config_service.return_value.resolve_skill_file_member_path.return_value = "references/guide.md"
|
||||
response = raw(AgentConfigSkillFileDownloadApi(), unbound_session, _USER, _APP, "alpha")
|
||||
assert response.status_code == 200
|
||||
assert response.get_json()["url"].endswith(
|
||||
"/apps/app-1/agent/config/skills/alpha/files/content?node_id=node-1&draft_type=debug_build&path=references%2Fguide.md"
|
||||
@@ -406,25 +419,31 @@ def test_skill_file_download_api_forwards_workflow_node_and_draft_type(unbound_s
|
||||
|
||||
def test_skill_file_download_api_propagates_member_lookup_404s(unbound_session: Session) -> None:
|
||||
raw = _raw(AgentConfigSkillFileDownloadApi.get)
|
||||
with app.test_request_context("/?node_id=node-1&path=references/missing.md"):
|
||||
with patch(f"{_MOD}.AgentComposerService") as composer, patch(f"{_MOD}.AgentConfigService") as config_service:
|
||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9"
|
||||
composer.load_agent_composer.return_value = {"draft": {"id": "draft-1"}}
|
||||
config_service.return_value.resolve_skill_file_member_path.side_effect = AgentConfigServiceError(
|
||||
"config_skill_file_not_found", "missing", status_code=404
|
||||
)
|
||||
body, status = raw(AgentConfigSkillFileDownloadApi(), unbound_session, _USER, _APP, "alpha")
|
||||
with (
|
||||
app.test_request_context("/?node_id=node-1&path=references/missing.md"),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9"
|
||||
composer.load_agent_composer.return_value = {"draft": {"id": "draft-1"}}
|
||||
config_service.return_value.resolve_skill_file_member_path.side_effect = AgentConfigServiceError(
|
||||
"config_skill_file_not_found", "missing", status_code=404
|
||||
)
|
||||
body, status = raw(AgentConfigSkillFileDownloadApi(), unbound_session, _USER, _APP, "alpha")
|
||||
assert status == 404
|
||||
assert body["code"] == "config_skill_file_not_found"
|
||||
|
||||
|
||||
def test_file_download_api_returns_signed_url_json(unbound_session: Session) -> None:
|
||||
raw = _raw(AgentConfigFileDownloadApi.get)
|
||||
with app.test_request_context("/?node_id=node-1"):
|
||||
with patch(f"{_MOD}.AgentComposerService") as composer, patch(f"{_MOD}.AgentConfigService") as config_service:
|
||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9"
|
||||
composer.load_agent_composer.return_value = {"draft": {"id": "draft-1"}}
|
||||
config_service.return_value.download_file_url.return_value = "https://example.com/guide.txt"
|
||||
response = raw(AgentConfigFileDownloadApi(), unbound_session, _USER, _APP, "guide.txt")
|
||||
with (
|
||||
app.test_request_context("/?node_id=node-1"),
|
||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||
patch(f"{_MOD}.AgentConfigService") as config_service,
|
||||
):
|
||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9"
|
||||
composer.load_agent_composer.return_value = {"draft": {"id": "draft-1"}}
|
||||
config_service.return_value.download_file_url.return_value = "https://example.com/guide.txt"
|
||||
response = raw(AgentConfigFileDownloadApi(), unbound_session, _USER, _APP, "guide.txt")
|
||||
assert response.status_code == 200
|
||||
assert response.get_json() == {"url": "https://example.com/guide.txt"}
|
||||
|
||||
@@ -114,9 +114,9 @@ class TestAgentManageRequiredForAgentApp:
|
||||
patches[0],
|
||||
patches[1],
|
||||
patch("controllers.console.app.wraps.enforce_rbac_access", side_effect=Forbidden()),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
view(app_id="app-1")
|
||||
view(app_id="app-1")
|
||||
|
||||
assert calls == []
|
||||
|
||||
@@ -136,9 +136,8 @@ class TestAgentManageRequiredForAgentApp:
|
||||
_persist_app(self.sqlite_session, scope=AgentScope.WORKFLOW_ONLY)
|
||||
patches = _patch_guard(self.account, rbac_enabled=False)
|
||||
|
||||
with patches[0], patches[1]:
|
||||
with pytest.raises(AppNotFoundError):
|
||||
view(app_id="app-1")
|
||||
with patches[0], patches[1], pytest.raises(AppNotFoundError):
|
||||
view(app_id="app-1")
|
||||
|
||||
assert calls == []
|
||||
|
||||
@@ -147,9 +146,13 @@ class TestAgentManageRequiredForAgentApp:
|
||||
_persist_app(self.sqlite_session, scope=AgentScope.WORKFLOW_ONLY)
|
||||
patches = _patch_guard(self.account, rbac_enabled=True)
|
||||
|
||||
with patches[0], patches[1], patch("controllers.console.app.wraps.enforce_rbac_access") as gate:
|
||||
with pytest.raises(AppNotFoundError):
|
||||
view(app_id="app-1")
|
||||
with (
|
||||
patches[0],
|
||||
patches[1],
|
||||
patch("controllers.console.app.wraps.enforce_rbac_access") as gate,
|
||||
pytest.raises(AppNotFoundError),
|
||||
):
|
||||
view(app_id="app-1")
|
||||
|
||||
gate.assert_not_called()
|
||||
assert calls == []
|
||||
|
||||
@@ -165,9 +165,9 @@ def test_get_app_ref_raises_not_found_when_app_is_not_in_current_tenant(sqlite_s
|
||||
"current_account_with_tenant",
|
||||
return_value=(_account(), "tenant-2"),
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
annotation_module._get_app_ref(sqlite_session, "app-1")
|
||||
annotation_module._get_app_ref(sqlite_session, "app-1")
|
||||
|
||||
|
||||
class TestConsoleAnnotationRefBoundaries:
|
||||
|
||||
@@ -288,16 +288,18 @@ class TestAnnotationImportServiceValidation:
|
||||
with patch("services.annotation_service.FeatureService") as mock_features:
|
||||
mock_features.get_features.return_value.billing.enabled = False
|
||||
|
||||
with patch("services.annotation_service.batch_import_annotations_task") as mock_task:
|
||||
with patch("services.annotation_service.redis_client"):
|
||||
result = AppAnnotationService.batch_import_app_annotations("app_id", file, sqlite_session)
|
||||
with (
|
||||
patch("services.annotation_service.batch_import_annotations_task") as mock_task,
|
||||
patch("services.annotation_service.redis_client"),
|
||||
):
|
||||
result = AppAnnotationService.batch_import_app_annotations("app_id", file, sqlite_session)
|
||||
|
||||
# Should return success response
|
||||
assert "job_id" in result
|
||||
assert "job_status" in result
|
||||
assert result["job_status"] == "waiting"
|
||||
assert "record_count" in result
|
||||
assert result["record_count"] == 2
|
||||
# Should return success response
|
||||
assert "job_id" in result
|
||||
assert "job_status" in result
|
||||
assert result["job_status"] == "waiting"
|
||||
assert "record_count" in result
|
||||
assert result["record_count"] == 2
|
||||
|
||||
|
||||
class TestAnnotationImportTaskOptimization:
|
||||
|
||||
@@ -409,16 +409,18 @@ class TestOpsTraceEndpoints:
|
||||
lambda **_kwargs: {"error": True},
|
||||
)
|
||||
|
||||
with app.test_request_context(
|
||||
"/",
|
||||
json={"tracing_provider": "langfuse", "tracing_config": {"api_key": "k"}},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/",
|
||||
json={"tracing_provider": "langfuse", "tracing_config": {"api_key": "k"}},
|
||||
),
|
||||
pytest.raises(BadRequest),
|
||||
):
|
||||
with pytest.raises(BadRequest):
|
||||
method(
|
||||
api,
|
||||
TraceConfigPayload(tracing_provider="langfuse", tracing_config={"api_key": "k"}),
|
||||
_make_app(),
|
||||
)
|
||||
method(
|
||||
api,
|
||||
TraceConfigPayload(tracing_provider="langfuse", tracing_config={"api_key": "k"}),
|
||||
_make_app(),
|
||||
)
|
||||
|
||||
def test_trace_app_config_delete_not_found(self, app: Flask, monkeypatch: pytest.MonkeyPatch):
|
||||
api = ops_trace_module.TraceAppConfigApi()
|
||||
@@ -430,9 +432,8 @@ class TestOpsTraceEndpoints:
|
||||
lambda **_kwargs: False,
|
||||
)
|
||||
|
||||
with app.test_request_context("/?tracing_provider=langfuse"):
|
||||
with pytest.raises(BadRequest):
|
||||
method(api, TraceProviderQuery(tracing_provider="langfuse"), _make_app())
|
||||
with app.test_request_context("/?tracing_provider=langfuse"), pytest.raises(BadRequest):
|
||||
method(api, TraceProviderQuery(tracing_provider="langfuse"), _make_app())
|
||||
|
||||
|
||||
class TestSiteEndpoints:
|
||||
|
||||
@@ -612,40 +612,39 @@ def test_app_create_api_attaches_permission_keys(
|
||||
permission_keys=[],
|
||||
)
|
||||
|
||||
with app.test_request_context("/apps", method="POST", json={}):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
app_module.console_ns.payload = {
|
||||
"name": "Created App",
|
||||
"description": "Summary",
|
||||
"mode": "advanced-chat",
|
||||
}
|
||||
monkeypatch.setattr(
|
||||
app_module,
|
||||
"AppService",
|
||||
lambda: SimpleNamespace(create_app=lambda tenant_id, params, user, session: app_obj),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.AppPermissions,
|
||||
"batch_get",
|
||||
lambda tenant_id, account_id, app_ids, session: {"app-new": ["app.acl.view_layout", "app.acl.edit"]},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.AppAccess,
|
||||
"replace_whitelist",
|
||||
MagicMock(),
|
||||
)
|
||||
monkeypatch.setattr(app_module.initialize_created_app_rbac_access_task, "delay", MagicMock())
|
||||
resp, status = method(
|
||||
app_module.AppListApi(),
|
||||
app_module.CreateAppPayload(
|
||||
name="Created App",
|
||||
description="Summary",
|
||||
mode="advanced-chat",
|
||||
),
|
||||
unbound_session,
|
||||
"tenant-1",
|
||||
SimpleNamespace(id="acct-1"),
|
||||
)
|
||||
with app.test_request_context("/apps", method="POST", json={}), pytest.MonkeyPatch.context() as monkeypatch:
|
||||
app_module.console_ns.payload = {
|
||||
"name": "Created App",
|
||||
"description": "Summary",
|
||||
"mode": "advanced-chat",
|
||||
}
|
||||
monkeypatch.setattr(
|
||||
app_module,
|
||||
"AppService",
|
||||
lambda: SimpleNamespace(create_app=lambda tenant_id, params, user, session: app_obj),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.AppPermissions,
|
||||
"batch_get",
|
||||
lambda tenant_id, account_id, app_ids, session: {"app-new": ["app.acl.view_layout", "app.acl.edit"]},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.AppAccess,
|
||||
"replace_whitelist",
|
||||
MagicMock(),
|
||||
)
|
||||
monkeypatch.setattr(app_module.initialize_created_app_rbac_access_task, "delay", MagicMock())
|
||||
resp, status = method(
|
||||
app_module.AppListApi(),
|
||||
app_module.CreateAppPayload(
|
||||
name="Created App",
|
||||
description="Summary",
|
||||
mode="advanced-chat",
|
||||
),
|
||||
unbound_session,
|
||||
"tenant-1",
|
||||
SimpleNamespace(id="acct-1"),
|
||||
)
|
||||
|
||||
assert status == 201
|
||||
assert resp["permission_keys"] == ["app.acl.view_layout", "app.acl.edit"]
|
||||
@@ -672,40 +671,39 @@ def test_app_list_api_attaches_permission_keys(
|
||||
pagination = SimpleNamespace(page=1, per_page=20, total=1, has_next=False, items=[app_obj])
|
||||
get_paginate_apps = MagicMock(return_value=pagination)
|
||||
|
||||
with app.test_request_context("/apps"):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(
|
||||
app_module.AppService,
|
||||
"get_paginate_apps",
|
||||
get_paginate_apps,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.SystemFeatureService,
|
||||
"is_webapp_auth_enabled",
|
||||
lambda: False,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
lambda tenant_id, account_id, session: app_module.enterprise_rbac_service.MyPermissionsResponse(
|
||||
app=app_module.enterprise_rbac_service.ResourcePermissionSnapshot(
|
||||
default_permission_keys=["app.preview", "app.acl.view_layout"],
|
||||
overrides=[
|
||||
app_module.enterprise_rbac_service.ResourcePermissionKeys(
|
||||
resource_id="app-1",
|
||||
permission_keys=["app.acl.view_layout", "app.acl.edit"],
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.AppAccess,
|
||||
"whitelist_resources",
|
||||
lambda tenant_id, account_id: SimpleNamespace(unrestricted=True, resource_ids=[]),
|
||||
)
|
||||
with app.test_request_context("/apps"), pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(
|
||||
app_module.AppService,
|
||||
"get_paginate_apps",
|
||||
get_paginate_apps,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.SystemFeatureService,
|
||||
"is_webapp_auth_enabled",
|
||||
lambda: False,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
lambda tenant_id, account_id, session: app_module.enterprise_rbac_service.MyPermissionsResponse(
|
||||
app=app_module.enterprise_rbac_service.ResourcePermissionSnapshot(
|
||||
default_permission_keys=["app.preview", "app.acl.view_layout"],
|
||||
overrides=[
|
||||
app_module.enterprise_rbac_service.ResourcePermissionKeys(
|
||||
resource_id="app-1",
|
||||
permission_keys=["app.acl.view_layout", "app.acl.edit"],
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.AppAccess,
|
||||
"whitelist_resources",
|
||||
lambda tenant_id, account_id: SimpleNamespace(unrestricted=True, resource_ids=[]),
|
||||
)
|
||||
|
||||
resp, status = method(app_module.AppListApi(), "tenant-1", "acct-1", sqlite_session)
|
||||
resp, status = method(app_module.AppListApi(), "tenant-1", "acct-1", sqlite_session)
|
||||
|
||||
assert status == 200
|
||||
params = get_paginate_apps.call_args.args[2]
|
||||
@@ -738,25 +736,24 @@ def test_recent_app_list_api_returns_only_home_card_fields(
|
||||
)
|
||||
get_recent_apps = MagicMock(return_value=[recent_app])
|
||||
|
||||
with app.test_request_context("/apps/recent?limit=8"):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(app_module.AppService, "get_recent_apps", get_recent_apps)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
lambda tenant_id, account_id, session: app_module.enterprise_rbac_service.MyPermissionsResponse(
|
||||
app=app_module.enterprise_rbac_service.ResourcePermissionSnapshot(
|
||||
overrides=[
|
||||
app_module.enterprise_rbac_service.ResourcePermissionKeys(
|
||||
resource_id="app-1",
|
||||
permission_keys=["app.acl.monitor"],
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
)
|
||||
with app.test_request_context("/apps/recent?limit=8"), pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(app_module.AppService, "get_recent_apps", get_recent_apps)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
lambda tenant_id, account_id, session: app_module.enterprise_rbac_service.MyPermissionsResponse(
|
||||
app=app_module.enterprise_rbac_service.ResourcePermissionSnapshot(
|
||||
overrides=[
|
||||
app_module.enterprise_rbac_service.ResourcePermissionKeys(
|
||||
resource_id="app-1",
|
||||
permission_keys=["app.acl.monitor"],
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
resp, status = method(app_module.RecentAppListApi(), "tenant-1", "acct-1", unbound_session)
|
||||
resp, status = method(app_module.RecentAppListApi(), "tenant-1", "acct-1", unbound_session)
|
||||
|
||||
assert status == 200
|
||||
assert resp == {
|
||||
@@ -806,28 +803,27 @@ def test_recent_app_list_api_applies_rbac_visibility_filter(
|
||||
method = method.__wrapped__
|
||||
|
||||
get_recent_apps = MagicMock(return_value=[])
|
||||
with app.test_request_context("/apps/recent"):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(app_module.AppService, "get_recent_apps", get_recent_apps)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
lambda tenant_id, account_id, session: app_module.enterprise_rbac_service.MyPermissionsResponse(
|
||||
workspace=app_module.enterprise_rbac_service.WorkspacePermissionSnapshot(
|
||||
permission_keys=["app.create_and_management"]
|
||||
)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.AppAccess,
|
||||
"whitelist_resources",
|
||||
lambda tenant_id, account_id: SimpleNamespace(
|
||||
unrestricted=False,
|
||||
resource_ids=["app-shared"],
|
||||
),
|
||||
)
|
||||
with app.test_request_context("/apps/recent"), pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(app_module.AppService, "get_recent_apps", get_recent_apps)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
lambda tenant_id, account_id, session: app_module.enterprise_rbac_service.MyPermissionsResponse(
|
||||
workspace=app_module.enterprise_rbac_service.WorkspacePermissionSnapshot(
|
||||
permission_keys=["app.create_and_management"]
|
||||
)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.AppAccess,
|
||||
"whitelist_resources",
|
||||
lambda tenant_id, account_id: SimpleNamespace(
|
||||
unrestricted=False,
|
||||
resource_ids=["app-shared"],
|
||||
),
|
||||
)
|
||||
|
||||
resp, status = method(app_module.RecentAppListApi(), "tenant-1", "acct-1", unbound_session)
|
||||
resp, status = method(app_module.RecentAppListApi(), "tenant-1", "acct-1", unbound_session)
|
||||
|
||||
assert status == 200
|
||||
assert resp == {"data": []}
|
||||
@@ -847,30 +843,29 @@ def test_app_list_api_limits_to_apps_created_by_current_user_without_view_permis
|
||||
pagination = SimpleNamespace(page=1, per_page=20, total=0, has_next=False, items=[])
|
||||
get_paginate_apps = MagicMock(return_value=pagination)
|
||||
|
||||
with app.test_request_context("/apps"):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(app_module.AppService, "get_paginate_apps", get_paginate_apps)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
lambda tenant_id, account_id, session: app_module.enterprise_rbac_service.MyPermissionsResponse(
|
||||
workspace=app_module.enterprise_rbac_service.WorkspacePermissionSnapshot(
|
||||
permission_keys=["app.create_and_management"]
|
||||
)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.AppAccess,
|
||||
"whitelist_resources",
|
||||
lambda tenant_id, account_id: SimpleNamespace(resource_ids=["app-shared", "app-not-permitted"]),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.SystemFeatureService,
|
||||
"is_webapp_auth_enabled",
|
||||
lambda: False,
|
||||
)
|
||||
with app.test_request_context("/apps"), pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(app_module.AppService, "get_paginate_apps", get_paginate_apps)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
lambda tenant_id, account_id, session: app_module.enterprise_rbac_service.MyPermissionsResponse(
|
||||
workspace=app_module.enterprise_rbac_service.WorkspacePermissionSnapshot(
|
||||
permission_keys=["app.create_and_management"]
|
||||
)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.AppAccess,
|
||||
"whitelist_resources",
|
||||
lambda tenant_id, account_id: SimpleNamespace(resource_ids=["app-shared", "app-not-permitted"]),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.SystemFeatureService,
|
||||
"is_webapp_auth_enabled",
|
||||
lambda: False,
|
||||
)
|
||||
|
||||
resp, status = method(app_module.AppListApi(), "tenant-1", "acct-1", unbound_session)
|
||||
resp, status = method(app_module.AppListApi(), "tenant-1", "acct-1", unbound_session)
|
||||
|
||||
assert status == 200
|
||||
assert resp["data"] == []
|
||||
@@ -891,43 +886,42 @@ def test_app_list_api_limits_to_preview_overrides_without_manage_own_permission(
|
||||
pagination = SimpleNamespace(page=1, per_page=20, total=0, has_next=False, items=[])
|
||||
get_paginate_apps = MagicMock(return_value=pagination)
|
||||
|
||||
with app.test_request_context("/apps"):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(app_module.AppService, "get_paginate_apps", get_paginate_apps)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
lambda tenant_id, account_id, session: app_module.enterprise_rbac_service.MyPermissionsResponse(
|
||||
app=app_module.enterprise_rbac_service.ResourcePermissionSnapshot(
|
||||
overrides=[
|
||||
app_module.enterprise_rbac_service.ResourcePermissionKeys(
|
||||
resource_id="app-acl-shared",
|
||||
permission_keys=["app.acl.preview"],
|
||||
),
|
||||
app_module.enterprise_rbac_service.ResourcePermissionKeys(
|
||||
resource_id="app-full",
|
||||
permission_keys=["app.full_access"],
|
||||
),
|
||||
app_module.enterprise_rbac_service.ResourcePermissionKeys(
|
||||
resource_id="app-shared",
|
||||
permission_keys=["app.preview"],
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.AppAccess,
|
||||
"whitelist_resources",
|
||||
lambda tenant_id, account_id: SimpleNamespace(resource_ids=["app-whitelist-only"]),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.SystemFeatureService,
|
||||
"is_webapp_auth_enabled",
|
||||
lambda: False,
|
||||
)
|
||||
with app.test_request_context("/apps"), pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(app_module.AppService, "get_paginate_apps", get_paginate_apps)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
lambda tenant_id, account_id, session: app_module.enterprise_rbac_service.MyPermissionsResponse(
|
||||
app=app_module.enterprise_rbac_service.ResourcePermissionSnapshot(
|
||||
overrides=[
|
||||
app_module.enterprise_rbac_service.ResourcePermissionKeys(
|
||||
resource_id="app-acl-shared",
|
||||
permission_keys=["app.acl.preview"],
|
||||
),
|
||||
app_module.enterprise_rbac_service.ResourcePermissionKeys(
|
||||
resource_id="app-full",
|
||||
permission_keys=["app.full_access"],
|
||||
),
|
||||
app_module.enterprise_rbac_service.ResourcePermissionKeys(
|
||||
resource_id="app-shared",
|
||||
permission_keys=["app.preview"],
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.AppAccess,
|
||||
"whitelist_resources",
|
||||
lambda tenant_id, account_id: SimpleNamespace(resource_ids=["app-whitelist-only"]),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.SystemFeatureService,
|
||||
"is_webapp_auth_enabled",
|
||||
lambda: False,
|
||||
)
|
||||
|
||||
method(app_module.AppListApi(), "tenant-1", "acct-1", unbound_session)
|
||||
method(app_module.AppListApi(), "tenant-1", "acct-1", unbound_session)
|
||||
|
||||
params = get_paginate_apps.call_args.args[2]
|
||||
assert params.accessible_app_ids == ["app-whitelist-only"]
|
||||
@@ -946,26 +940,25 @@ def test_app_list_api_returns_no_apps_without_workspace_or_resource_view_permiss
|
||||
pagination = SimpleNamespace(page=1, per_page=20, total=0, has_next=False, items=[])
|
||||
get_paginate_apps = MagicMock(return_value=pagination)
|
||||
|
||||
with app.test_request_context("/apps"):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(app_module.AppService, "get_paginate_apps", get_paginate_apps)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
lambda tenant_id, account_id, session: app_module.enterprise_rbac_service.MyPermissionsResponse(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.AppAccess,
|
||||
"whitelist_resources",
|
||||
lambda tenant_id, account_id: SimpleNamespace(resource_ids=["app-not-permitted"]),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.SystemFeatureService,
|
||||
"is_webapp_auth_enabled",
|
||||
lambda: False,
|
||||
)
|
||||
with app.test_request_context("/apps"), pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(app_module.AppService, "get_paginate_apps", get_paginate_apps)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
lambda tenant_id, account_id, session: app_module.enterprise_rbac_service.MyPermissionsResponse(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.AppAccess,
|
||||
"whitelist_resources",
|
||||
lambda tenant_id, account_id: SimpleNamespace(resource_ids=["app-not-permitted"]),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.SystemFeatureService,
|
||||
"is_webapp_auth_enabled",
|
||||
lambda: False,
|
||||
)
|
||||
|
||||
method(app_module.AppListApi(), "tenant-1", "acct-1", unbound_session)
|
||||
method(app_module.AppListApi(), "tenant-1", "acct-1", unbound_session)
|
||||
|
||||
params = get_paginate_apps.call_args.args[2]
|
||||
assert params.accessible_app_ids == ["app-not-permitted"]
|
||||
@@ -991,45 +984,44 @@ def test_app_detail_api_attaches_current_user_permission_keys(
|
||||
permission_keys=[],
|
||||
)
|
||||
|
||||
with app.test_request_context("/apps/app-1"):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
get_app = MagicMock(return_value=app_obj)
|
||||
monkeypatch.setattr(app_module, "AppService", lambda: SimpleNamespace(get_app=get_app))
|
||||
monkeypatch.setattr(
|
||||
app_module.SystemFeatureService,
|
||||
"is_webapp_auth_enabled",
|
||||
lambda: False,
|
||||
)
|
||||
get_permissions = MagicMock(
|
||||
return_value=app_module.enterprise_rbac_service.MyPermissionsResponse(
|
||||
app=app_module.enterprise_rbac_service.ResourcePermissionSnapshot(
|
||||
overrides=[
|
||||
app_module.enterprise_rbac_service.ResourcePermissionKeys(
|
||||
resource_id="app-1",
|
||||
permission_keys=[
|
||||
"app.acl.view_layout",
|
||||
"app.acl.edit",
|
||||
"app.acl.deploy",
|
||||
"app.acl.monitor",
|
||||
],
|
||||
)
|
||||
]
|
||||
)
|
||||
with app.test_request_context("/apps/app-1"), pytest.MonkeyPatch.context() as monkeypatch:
|
||||
get_app = MagicMock(return_value=app_obj)
|
||||
monkeypatch.setattr(app_module, "AppService", lambda: SimpleNamespace(get_app=get_app))
|
||||
monkeypatch.setattr(
|
||||
app_module.SystemFeatureService,
|
||||
"is_webapp_auth_enabled",
|
||||
lambda: False,
|
||||
)
|
||||
get_permissions = MagicMock(
|
||||
return_value=app_module.enterprise_rbac_service.MyPermissionsResponse(
|
||||
app=app_module.enterprise_rbac_service.ResourcePermissionSnapshot(
|
||||
overrides=[
|
||||
app_module.enterprise_rbac_service.ResourcePermissionKeys(
|
||||
resource_id="app-1",
|
||||
permission_keys=[
|
||||
"app.acl.view_layout",
|
||||
"app.acl.edit",
|
||||
"app.acl.deploy",
|
||||
"app.acl.monitor",
|
||||
],
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
get_permissions,
|
||||
)
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
get_permissions,
|
||||
)
|
||||
|
||||
resp = method(
|
||||
app_module.AppApi(),
|
||||
unbound_session,
|
||||
"tenant-1",
|
||||
SimpleNamespace(id="acct-1"),
|
||||
app_model=app_obj,
|
||||
)
|
||||
resp = method(
|
||||
app_module.AppApi(),
|
||||
unbound_session,
|
||||
"tenant-1",
|
||||
SimpleNamespace(id="acct-1"),
|
||||
app_model=app_obj,
|
||||
)
|
||||
|
||||
get_app.assert_called_once_with(app_obj, session=unbound_session)
|
||||
get_permissions.assert_called_once_with("tenant-1", "acct-1", app_id="app-1", session=unbound_session)
|
||||
@@ -1070,35 +1062,37 @@ def test_app_copy_api_attaches_permission_keys(
|
||||
|
||||
import_result = SimpleNamespace(status=app_module.ImportStatus.COMPLETED, app_id=app_obj.id)
|
||||
|
||||
with app.test_request_context("/apps/app-original/copy", method="POST", json={}):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(
|
||||
app_module,
|
||||
"AppDslService",
|
||||
lambda *_args, **_kwargs: SimpleNamespace(
|
||||
export_dsl=lambda **_kwargs: "dsl",
|
||||
import_app=lambda **_kwargs: import_result,
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.SystemFeatureService,
|
||||
"is_webapp_auth_enabled",
|
||||
lambda: False,
|
||||
)
|
||||
monkeypatch.setattr(app_module, "db", SimpleNamespace(engine=sqlite_engine))
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.AppPermissions,
|
||||
"batch_get",
|
||||
lambda tenant_id, account_id, app_ids, session: {app_obj.id: ["app.acl.view_layout", "app.acl.edit"]},
|
||||
)
|
||||
with (
|
||||
app.test_request_context("/apps/app-original/copy", method="POST", json={}),
|
||||
pytest.MonkeyPatch.context() as monkeypatch,
|
||||
):
|
||||
monkeypatch.setattr(
|
||||
app_module,
|
||||
"AppDslService",
|
||||
lambda *_args, **_kwargs: SimpleNamespace(
|
||||
export_dsl=lambda **_kwargs: "dsl",
|
||||
import_app=lambda **_kwargs: import_result,
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.SystemFeatureService,
|
||||
"is_webapp_auth_enabled",
|
||||
lambda: False,
|
||||
)
|
||||
monkeypatch.setattr(app_module, "db", SimpleNamespace(engine=sqlite_engine))
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.AppPermissions,
|
||||
"batch_get",
|
||||
lambda tenant_id, account_id, app_ids, session: {app_obj.id: ["app.acl.view_layout", "app.acl.edit"]},
|
||||
)
|
||||
|
||||
resp, status = method(
|
||||
app_module.AppCopyApi(),
|
||||
app_module.CopyAppPayload(),
|
||||
"tenant-1",
|
||||
SimpleNamespace(id="acct-1"),
|
||||
app_model=SimpleNamespace(id="app-original"),
|
||||
)
|
||||
resp, status = method(
|
||||
app_module.AppCopyApi(),
|
||||
app_module.CopyAppPayload(),
|
||||
"tenant-1",
|
||||
SimpleNamespace(id="acct-1"),
|
||||
app_model=SimpleNamespace(id="app-original"),
|
||||
)
|
||||
|
||||
assert status == 201
|
||||
assert sqlite_session.get(App, app_obj.id) is not None
|
||||
|
||||
@@ -221,19 +221,21 @@ def test_agent_console_audio_api_checks_rbac_with_backing_app_id(
|
||||
|
||||
api = AgentChatMessageAudioApi()
|
||||
handler = unwrap(api.post)
|
||||
with app.test_request_context(
|
||||
f"/console/api/agent/{agent_id}/audio-to-text",
|
||||
method="POST",
|
||||
data={"file": _file_data()},
|
||||
with (
|
||||
app.test_request_context(
|
||||
f"/console/api/agent/{agent_id}/audio-to-text",
|
||||
method="POST",
|
||||
data={"file": _file_data()},
|
||||
),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
handler(
|
||||
api,
|
||||
session=unbound_session,
|
||||
current_tenant_id="tenant-1",
|
||||
current_user=_account(),
|
||||
agent_id=agent_id,
|
||||
)
|
||||
handler(
|
||||
api,
|
||||
session=unbound_session,
|
||||
current_tenant_id="tenant-1",
|
||||
current_user=_account(),
|
||||
agent_id=agent_id,
|
||||
)
|
||||
|
||||
assert soul_loaded is False
|
||||
|
||||
@@ -255,19 +257,21 @@ def test_agent_console_audio_api_preserves_missing_build_draft_404(
|
||||
|
||||
api = AgentChatMessageAudioApi()
|
||||
handler = unwrap(api.post)
|
||||
with app.test_request_context(
|
||||
f"/console/api/agent/{agent_id}/audio-to-text",
|
||||
method="POST",
|
||||
data={"file": _file_data(), "draft_type": "debug_build"},
|
||||
with (
|
||||
app.test_request_context(
|
||||
f"/console/api/agent/{agent_id}/audio-to-text",
|
||||
method="POST",
|
||||
data={"file": _file_data(), "draft_type": "debug_build"},
|
||||
),
|
||||
pytest.raises(AgentVersionNotFoundError),
|
||||
):
|
||||
with pytest.raises(AgentVersionNotFoundError):
|
||||
handler(
|
||||
api,
|
||||
session=unbound_session,
|
||||
current_tenant_id="tenant-1",
|
||||
current_user=_account(),
|
||||
agent_id=agent_id,
|
||||
)
|
||||
handler(
|
||||
api,
|
||||
session=unbound_session,
|
||||
current_tenant_id="tenant-1",
|
||||
current_user=_account(),
|
||||
agent_id=agent_id,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -291,9 +295,11 @@ def test_console_audio_api_error_mapping(app: Flask, monkeypatch: pytest.MonkeyP
|
||||
handler = unwrap(api.post)
|
||||
app_model = _app()
|
||||
|
||||
with app.test_request_context("/console/api/apps/app/audio-to-text", method="POST", data={"file": _file_data()}):
|
||||
with pytest.raises(expected):
|
||||
handler(api, app_model=app_model)
|
||||
with (
|
||||
app.test_request_context("/console/api/apps/app/audio-to-text", method="POST", data={"file": _file_data()}),
|
||||
pytest.raises(expected),
|
||||
):
|
||||
handler(api, app_model=app_model)
|
||||
|
||||
|
||||
def test_console_audio_api_unhandled_error(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -302,9 +308,11 @@ def test_console_audio_api_unhandled_error(app: Flask, monkeypatch: pytest.Monke
|
||||
handler = unwrap(api.post)
|
||||
app_model = _app()
|
||||
|
||||
with app.test_request_context("/console/api/apps/app/audio-to-text", method="POST", data={"file": _file_data()}):
|
||||
with pytest.raises(InternalServerError):
|
||||
handler(api, app_model=app_model)
|
||||
with (
|
||||
app.test_request_context("/console/api/apps/app/audio-to-text", method="POST", data={"file": _file_data()}),
|
||||
pytest.raises(InternalServerError),
|
||||
):
|
||||
handler(api, app_model=app_model)
|
||||
|
||||
|
||||
def test_console_text_api_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -357,13 +365,15 @@ def test_console_text_api_error_mapping(app: Flask, monkeypatch: pytest.MonkeyPa
|
||||
handler = unwrap(api.post)
|
||||
app_model = _app()
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/apps/app/text-to-audio",
|
||||
method="POST",
|
||||
json={"text": "hello"},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/apps/app/text-to-audio",
|
||||
method="POST",
|
||||
json={"text": "hello"},
|
||||
),
|
||||
pytest.raises(ProviderQuotaExceededError),
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
handler(api, TextToSpeechPayload(text="hello"), app_model=app_model)
|
||||
handler(api, TextToSpeechPayload(text="hello"), app_model=app_model)
|
||||
|
||||
|
||||
def test_console_text_modes_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -391,9 +401,11 @@ def test_console_text_modes_language_error(app: Flask, monkeypatch: pytest.Monke
|
||||
handler = unwrap(api.get)
|
||||
app_model = _app(tenant_id="t1")
|
||||
|
||||
with app.test_request_context("/console/api/apps/app/text-to-audio/voices?language=en", method="GET"):
|
||||
with pytest.raises(AppUnavailableError):
|
||||
handler(api, TextToSpeechVoiceQuery(language="en-US"), app_model=app_model)
|
||||
with (
|
||||
app.test_request_context("/console/api/apps/app/text-to-audio/voices?language=en", method="GET"),
|
||||
pytest.raises(AppUnavailableError),
|
||||
):
|
||||
handler(api, TextToSpeechVoiceQuery(language="en-US"), app_model=app_model)
|
||||
|
||||
|
||||
def test_audio_to_text_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -430,14 +442,16 @@ def test_audio_to_text_maps_audio_too_large(app: Flask, monkeypatch: pytest.Monk
|
||||
app_model = _app(app_id="app-1")
|
||||
|
||||
data = {"file": (io.BytesIO(b"x"), "sample.wav")}
|
||||
with app.test_request_context(
|
||||
"/console/api/apps/app-1/audio-to-text",
|
||||
method="POST",
|
||||
data=data,
|
||||
content_type="multipart/form-data",
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/apps/app-1/audio-to-text",
|
||||
method="POST",
|
||||
data=data,
|
||||
content_type="multipart/form-data",
|
||||
),
|
||||
pytest.raises(AudioTooLargeError),
|
||||
):
|
||||
with pytest.raises(AudioTooLargeError):
|
||||
method(api, app_model=app_model)
|
||||
method(api, app_model=app_model)
|
||||
|
||||
|
||||
def test_text_to_audio_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
||||
@@ -107,17 +107,19 @@ def test_completion_conversation_list_invalid_time_range(
|
||||
"parse_time_range",
|
||||
lambda *_args, **_kwargs: (_ for _ in ()).throw(ValueError("bad range")),
|
||||
)
|
||||
with app.test_request_context(
|
||||
"/console/api/apps/app-1/completion-conversations", method="GET", query_string={"start": "bad"}
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/apps/app-1/completion-conversations", method="GET", query_string={"start": "bad"}
|
||||
),
|
||||
pytest.raises(BadRequest),
|
||||
):
|
||||
with pytest.raises(BadRequest):
|
||||
method(
|
||||
api,
|
||||
conversation_module.CompletionConversationQuery(),
|
||||
unbound_session,
|
||||
account,
|
||||
app_model=_app(mode=AppMode.COMPLETION),
|
||||
)
|
||||
method(
|
||||
api,
|
||||
conversation_module.CompletionConversationQuery(),
|
||||
unbound_session,
|
||||
account,
|
||||
app_model=_app(mode=AppMode.COMPLETION),
|
||||
)
|
||||
|
||||
|
||||
def test_chat_conversation_list_advanced_chat_calls_paginate(
|
||||
|
||||
@@ -127,6 +127,8 @@ def test_get_conversation_variables_requires_conversation_id(app) -> None:
|
||||
api = conversation_variables_module.ConversationVariablesApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
with app.test_request_context("/console/api/apps/app-1/conversation-variables", method="GET"):
|
||||
with pytest.raises(ValidationError):
|
||||
conversation_variables_module.ConversationVariablesQuery.model_validate({})
|
||||
with (
|
||||
app.test_request_context("/console/api/apps/app-1/conversation-variables", method="GET"),
|
||||
pytest.raises(ValidationError),
|
||||
):
|
||||
conversation_variables_module.ConversationVariablesQuery.model_validate({})
|
||||
|
||||
@@ -94,13 +94,15 @@ def test_rule_code_generate_maps_token_error(app: Flask, monkeypatch: pytest.Mon
|
||||
|
||||
monkeypatch.setattr(generator_module.LLMGenerator, "generate_code", _raise)
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/rule-code-generate",
|
||||
method="POST",
|
||||
json={"instruction": "do it", "model_config": _model_config_payload()},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/rule-code-generate",
|
||||
method="POST",
|
||||
json={"instruction": "do it", "model_config": _model_config_payload()},
|
||||
),
|
||||
pytest.raises(ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, RuleCodeGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, RuleCodeGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sqlite_session", [(App,)], indirect=True)
|
||||
@@ -289,13 +291,15 @@ def test_instruction_template_invalid_type(app: Flask) -> None:
|
||||
api = generator_module.InstructionGenerationTemplateApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/instruction-generate/template",
|
||||
method="POST",
|
||||
json={"type": "unknown"},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/instruction-generate/template",
|
||||
method="POST",
|
||||
json={"type": "unknown"},
|
||||
),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, InstructionTemplatePayload.model_validate(request.get_json()))
|
||||
method(api, InstructionTemplatePayload.model_validate(request.get_json()))
|
||||
|
||||
|
||||
# ─ /workflow-generate ─────────────────────────────────────────────────────────
|
||||
@@ -382,13 +386,15 @@ def test_workflow_generate_maps_provider_token_error(app: Flask, monkeypatch: py
|
||||
|
||||
_stub_workflow_service(monkeypatch, raises=ProviderTokenNotInitError("missing token"))
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/workflow-generate",
|
||||
method="POST",
|
||||
json=_workflow_generate_payload(),
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/workflow-generate",
|
||||
method="POST",
|
||||
json=_workflow_generate_payload(),
|
||||
),
|
||||
pytest.raises(ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
|
||||
def test_workflow_generate_maps_quota_error(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -400,13 +406,15 @@ def test_workflow_generate_maps_quota_error(app: Flask, monkeypatch: pytest.Monk
|
||||
|
||||
_stub_workflow_service(monkeypatch, raises=QuotaExceededError())
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/workflow-generate",
|
||||
method="POST",
|
||||
json=_workflow_generate_payload(),
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/workflow-generate",
|
||||
method="POST",
|
||||
json=_workflow_generate_payload(),
|
||||
),
|
||||
pytest.raises(ProviderQuotaExceededError),
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
|
||||
def test_workflow_generate_maps_model_not_support_error(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -418,13 +426,15 @@ def test_workflow_generate_maps_model_not_support_error(app: Flask, monkeypatch:
|
||||
|
||||
_stub_workflow_service(monkeypatch, raises=ModelCurrentlyNotSupportError("not supported"))
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/workflow-generate",
|
||||
method="POST",
|
||||
json=_workflow_generate_payload(),
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/workflow-generate",
|
||||
method="POST",
|
||||
json=_workflow_generate_payload(),
|
||||
),
|
||||
pytest.raises(ProviderModelCurrentlyNotSupportError),
|
||||
):
|
||||
with pytest.raises(ProviderModelCurrentlyNotSupportError):
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
|
||||
def test_workflow_generate_maps_invoke_error(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -436,13 +446,15 @@ def test_workflow_generate_maps_invoke_error(app: Flask, monkeypatch: pytest.Mon
|
||||
|
||||
_stub_workflow_service(monkeypatch, raises=InvokeError("LLM unreachable"))
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/workflow-generate",
|
||||
method="POST",
|
||||
json=_workflow_generate_payload(),
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/workflow-generate",
|
||||
method="POST",
|
||||
json=_workflow_generate_payload(),
|
||||
),
|
||||
pytest.raises(CompletionRequestError),
|
||||
):
|
||||
with pytest.raises(CompletionRequestError):
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
|
||||
def test_workflow_generate_accepts_advanced_chat_mode(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
||||
@@ -47,13 +47,15 @@ def test_rule_generate_exceptions(app: Flask, monkeypatch: pytest.MonkeyPatch) -
|
||||
|
||||
monkeypatch.setattr(generator_module.LLMGenerator, "generate_rule_config", _raise)
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/rule-generate",
|
||||
method="POST",
|
||||
json={"instruction": "do it", "model_config": _model_config_payload()},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/rule-generate",
|
||||
method="POST",
|
||||
json={"instruction": "do it", "model_config": _model_config_payload()},
|
||||
),
|
||||
pytest.raises(expected_exception),
|
||||
):
|
||||
with pytest.raises(expected_exception):
|
||||
method(api, RuleGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, RuleGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
|
||||
def test_rule_code_generate_exceptions(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -73,13 +75,15 @@ def test_rule_code_generate_exceptions(app: Flask, monkeypatch: pytest.MonkeyPat
|
||||
|
||||
monkeypatch.setattr(generator_module.LLMGenerator, "generate_code", _raise)
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/rule-code-generate",
|
||||
method="POST",
|
||||
json={"instruction": "do it", "model_config": _model_config_payload()},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/rule-code-generate",
|
||||
method="POST",
|
||||
json={"instruction": "do it", "model_config": _model_config_payload()},
|
||||
),
|
||||
pytest.raises(expected_exception),
|
||||
):
|
||||
with pytest.raises(expected_exception):
|
||||
method(api, RuleCodeGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, RuleCodeGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
|
||||
def test_structured_output_generate_exceptions(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -100,13 +104,15 @@ def test_structured_output_generate_exceptions(app: Flask, monkeypatch: pytest.M
|
||||
|
||||
monkeypatch.setattr(generator_module.LLMGenerator, "generate_structured_output", _raise)
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/structured-output-generate",
|
||||
method="POST",
|
||||
json={"instruction": "do it", "model_config": _model_config_payload()},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/structured-output-generate",
|
||||
method="POST",
|
||||
json={"instruction": "do it", "model_config": _model_config_payload()},
|
||||
),
|
||||
pytest.raises(expected_exception),
|
||||
):
|
||||
with pytest.raises(expected_exception):
|
||||
method(api, RuleStructuredOutputPayload.model_validate(request.get_json()), "t1")
|
||||
method(api, RuleStructuredOutputPayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sqlite_session", [()], indirect=True)
|
||||
@@ -132,16 +138,18 @@ def test_instruction_generate_exceptions(
|
||||
|
||||
monkeypatch.setattr(generator_module.LLMGenerator, "instruction_modify_legacy", _raise)
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/instruction-generate",
|
||||
method="POST",
|
||||
json={
|
||||
"flow_id": "app-1",
|
||||
"node_id": "",
|
||||
"current": "old",
|
||||
"instruction": "do it",
|
||||
"model_config": _model_config_payload(),
|
||||
},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/instruction-generate",
|
||||
method="POST",
|
||||
json={
|
||||
"flow_id": "app-1",
|
||||
"node_id": "",
|
||||
"current": "old",
|
||||
"instruction": "do it",
|
||||
"model_config": _model_config_payload(),
|
||||
},
|
||||
),
|
||||
pytest.raises(expected_exception),
|
||||
):
|
||||
with pytest.raises(expected_exception):
|
||||
method(api, InstructionGeneratePayload.model_validate(request.get_json()), sqlite_session, "t1")
|
||||
method(api, InstructionGeneratePayload.model_validate(request.get_json()), sqlite_session, "t1")
|
||||
|
||||
@@ -116,10 +116,12 @@ def test_trace_config_mutations_require_edit_permission(
|
||||
service_mock = MagicMock(return_value=service_result)
|
||||
monkeypatch.setattr(ops_trace_module.OpsService, service_method_name, service_mock)
|
||||
|
||||
with app.test_request_context(path, method=method_name.upper(), json=payload):
|
||||
with _patch_payload(payload):
|
||||
with pytest.raises(Forbidden):
|
||||
getattr(ops_trace_module.TraceAppConfigApi(), method_name)(app_id="app-123")
|
||||
with (
|
||||
app.test_request_context(path, method=method_name.upper(), json=payload),
|
||||
_patch_payload(payload),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
getattr(ops_trace_module.TraceAppConfigApi(), method_name)(app_id="app-123")
|
||||
|
||||
service_mock.assert_not_called()
|
||||
|
||||
@@ -189,9 +191,11 @@ def test_trace_config_mutations_require_rbac_permission(
|
||||
service_mock = MagicMock(return_value=service_result)
|
||||
monkeypatch.setattr(ops_trace_module.OpsService, service_method_name, service_mock)
|
||||
|
||||
with app.test_request_context(path, method=method_name.upper(), json=payload):
|
||||
with _patch_payload(payload):
|
||||
with pytest.raises(Forbidden):
|
||||
getattr(ops_trace_module.TraceAppConfigApi(), method_name)(app_id="app-123")
|
||||
with (
|
||||
app.test_request_context(path, method=method_name.upper(), json=payload),
|
||||
_patch_payload(payload),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
getattr(ops_trace_module.TraceAppConfigApi(), method_name)(app_id="app-123")
|
||||
|
||||
service_mock.assert_not_called()
|
||||
|
||||
@@ -156,14 +156,16 @@ def test_daily_message_statistic_with_invalid_time_range(app: Flask, monkeypatch
|
||||
monkeypatch.setattr(statistic_module, "parse_time_range", mock_parse)
|
||||
monkeypatch.setattr(statistic_module, "convert_datetime_to_date", lambda field: field)
|
||||
|
||||
with app.test_request_context("/console/api/apps/app-1/statistics/daily-messages", method="GET"):
|
||||
with pytest.raises(BadRequest):
|
||||
method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
_account(),
|
||||
app_model=_app_model(),
|
||||
)
|
||||
with (
|
||||
app.test_request_context("/console/api/apps/app-1/statistics/daily-messages", method="GET"),
|
||||
pytest.raises(BadRequest),
|
||||
):
|
||||
method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
_account(),
|
||||
app_model=_app_model(),
|
||||
)
|
||||
|
||||
|
||||
def test_daily_message_statistic_multiple_rows(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
||||
@@ -210,9 +210,11 @@ def test_sync_draft_workflow_invalid_content_type(app: Flask, monkeypatch: pytes
|
||||
api = workflow_module.DraftWorkflowApi()
|
||||
handler = inspect.unwrap(api.post)
|
||||
|
||||
with app.test_request_context("/apps/app/workflows/draft", method="POST", data="x", content_type="text/html"):
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
handler(api, "t1", app_model=SimpleNamespace(id="app"))
|
||||
with (
|
||||
app.test_request_context("/apps/app/workflows/draft", method="POST", data="x", content_type="text/html"),
|
||||
pytest.raises(HTTPException) as exc,
|
||||
):
|
||||
handler(api, "t1", app_model=SimpleNamespace(id="app"))
|
||||
|
||||
assert exc.value.code == 415
|
||||
|
||||
@@ -358,13 +360,15 @@ def test_sync_draft_workflow_hash_mismatch(app: Flask, monkeypatch: pytest.Monke
|
||||
api = workflow_module.DraftWorkflowApi()
|
||||
handler = inspect.unwrap(api.post)
|
||||
|
||||
with app.test_request_context(
|
||||
"/apps/app/workflows/draft",
|
||||
method="POST",
|
||||
json={"graph": {}, "features": {}, "hash": "h"},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/apps/app/workflows/draft",
|
||||
method="POST",
|
||||
json={"graph": {}, "features": {}, "hash": "h"},
|
||||
),
|
||||
pytest.raises(DraftWorkflowNotSync),
|
||||
):
|
||||
with pytest.raises(DraftWorkflowNotSync):
|
||||
handler(api, "t1", app_model=SimpleNamespace(id="app"))
|
||||
handler(api, "t1", app_model=SimpleNamespace(id="app"))
|
||||
|
||||
|
||||
def test_sync_draft_workflow_variable_validation_error(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -379,13 +383,15 @@ def test_sync_draft_workflow_variable_validation_error(app: Flask, monkeypatch:
|
||||
api = workflow_module.DraftWorkflowApi()
|
||||
handler = inspect.unwrap(api.post)
|
||||
|
||||
with app.test_request_context(
|
||||
"/apps/app/workflows/draft",
|
||||
method="POST",
|
||||
json={"graph": {}, "features": {}, "hash": "h", "conversation_variables": [{"name": "topic"}]},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/apps/app/workflows/draft",
|
||||
method="POST",
|
||||
json={"graph": {}, "features": {}, "hash": "h", "conversation_variables": [{"name": "topic"}]},
|
||||
),
|
||||
pytest.raises(InvalidArgumentError) as exc,
|
||||
):
|
||||
with pytest.raises(InvalidArgumentError) as exc:
|
||||
handler(api, "t1", app_model=SimpleNamespace(id="app"))
|
||||
handler(api, "t1", app_model=SimpleNamespace(id="app"))
|
||||
|
||||
assert exc.value.description == "description too long"
|
||||
|
||||
@@ -436,17 +442,19 @@ def test_restore_published_workflow_to_draft_not_found(app: Flask, monkeypatch:
|
||||
api = workflow_module.DraftWorkflowRestoreApi()
|
||||
handler = inspect.unwrap(api.post)
|
||||
|
||||
with app.test_request_context(
|
||||
"/apps/app/workflows/published-workflow/restore",
|
||||
method="POST",
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/apps/app/workflows/published-workflow/restore",
|
||||
method="POST",
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
handler(
|
||||
api,
|
||||
"t1",
|
||||
app_model=SimpleNamespace(id="app", tenant_id="tenant-1"),
|
||||
workflow_id="published-workflow",
|
||||
)
|
||||
handler(
|
||||
api,
|
||||
"t1",
|
||||
app_model=SimpleNamespace(id="app", tenant_id="tenant-1"),
|
||||
workflow_id="published-workflow",
|
||||
)
|
||||
|
||||
|
||||
def test_restore_published_workflow_to_draft_returns_400_for_draft_source(
|
||||
@@ -468,17 +476,19 @@ def test_restore_published_workflow_to_draft_returns_400_for_draft_source(
|
||||
api = workflow_module.DraftWorkflowRestoreApi()
|
||||
handler = inspect.unwrap(api.post)
|
||||
|
||||
with app.test_request_context(
|
||||
"/apps/app/workflows/draft-workflow/restore",
|
||||
method="POST",
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/apps/app/workflows/draft-workflow/restore",
|
||||
method="POST",
|
||||
),
|
||||
pytest.raises(HTTPException) as exc,
|
||||
):
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
handler(
|
||||
api,
|
||||
"t1",
|
||||
app_model=SimpleNamespace(id="app", tenant_id="tenant-1"),
|
||||
workflow_id="draft-workflow",
|
||||
)
|
||||
handler(
|
||||
api,
|
||||
"t1",
|
||||
app_model=SimpleNamespace(id="app", tenant_id="tenant-1"),
|
||||
workflow_id="draft-workflow",
|
||||
)
|
||||
|
||||
assert exc.value.code == 400
|
||||
assert exc.value.description == workflow_module.RESTORE_SOURCE_WORKFLOW_MUST_BE_PUBLISHED_MESSAGE
|
||||
@@ -500,17 +510,19 @@ def test_restore_published_workflow_to_draft_returns_400_for_invalid_structure(
|
||||
api = workflow_module.DraftWorkflowRestoreApi()
|
||||
handler = inspect.unwrap(api.post)
|
||||
|
||||
with app.test_request_context(
|
||||
"/apps/app/workflows/published-workflow/restore",
|
||||
method="POST",
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/apps/app/workflows/published-workflow/restore",
|
||||
method="POST",
|
||||
),
|
||||
pytest.raises(HTTPException) as exc,
|
||||
):
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
handler(
|
||||
api,
|
||||
"t1",
|
||||
app_model=SimpleNamespace(id="app", tenant_id="tenant-1"),
|
||||
workflow_id="published-workflow",
|
||||
)
|
||||
handler(
|
||||
api,
|
||||
"t1",
|
||||
app_model=SimpleNamespace(id="app", tenant_id="tenant-1"),
|
||||
workflow_id="published-workflow",
|
||||
)
|
||||
|
||||
assert exc.value.code == 400
|
||||
assert exc.value.description == "invalid workflow graph"
|
||||
@@ -827,15 +839,17 @@ def test_advanced_chat_run_conversation_not_exists(app: Flask, monkeypatch: pyte
|
||||
|
||||
api = workflow_module.AdvancedChatDraftWorkflowRunApi()
|
||||
handler = inspect.unwrap(api.post)
|
||||
payload = workflow_module.AdvancedChatWorkflowRunPayload.model_validate({"inputs": {}})
|
||||
|
||||
with app.test_request_context(
|
||||
"/apps/app/advanced-chat/workflows/draft/run",
|
||||
method="POST",
|
||||
json={"inputs": {}},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/apps/app/advanced-chat/workflows/draft/run",
|
||||
method="POST",
|
||||
json={"inputs": {}},
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
payload = workflow_module.AdvancedChatWorkflowRunPayload.model_validate({"inputs": {}})
|
||||
with pytest.raises(NotFound):
|
||||
handler(api, payload, Mock(), "t1", app_model=SimpleNamespace(id="app"))
|
||||
handler(api, payload, Mock(), "t1", app_model=SimpleNamespace(id="app"))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -871,15 +885,17 @@ def test_trigger_run_loads_draft_with_request_session(
|
||||
app_model = SimpleNamespace(id="app-1")
|
||||
handler = inspect.unwrap(resource.post)
|
||||
|
||||
with app.test_request_context("/", method="POST", json=payload):
|
||||
with pytest.raises(ValueError, match="Workflow not found"):
|
||||
handler(
|
||||
resource(),
|
||||
payload_model.model_validate(payload),
|
||||
session,
|
||||
SimpleNamespace(id="account-1"),
|
||||
app_model,
|
||||
)
|
||||
with (
|
||||
app.test_request_context("/", method="POST", json=payload),
|
||||
pytest.raises(ValueError, match="Workflow not found"),
|
||||
):
|
||||
handler(
|
||||
resource(),
|
||||
payload_model.model_validate(payload),
|
||||
session,
|
||||
SimpleNamespace(id="account-1"),
|
||||
app_model,
|
||||
)
|
||||
|
||||
get_draft_workflow.assert_called_once_with(app_model, session=session)
|
||||
|
||||
@@ -1028,15 +1044,17 @@ def test_workflow_online_users_rejects_excessive_workflow_ids(app: Flask, monkey
|
||||
|
||||
api = workflow_module.WorkflowOnlineUsersApi()
|
||||
handler = inspect.unwrap(api.post)
|
||||
args = workflow_module.WorkflowOnlineUsersPayload.model_validate({"app_ids": excessive_ids})
|
||||
|
||||
with app.test_request_context(
|
||||
"/apps/workflows/online-users",
|
||||
method="POST",
|
||||
json={"app_ids": excessive_ids},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/apps/workflows/online-users",
|
||||
method="POST",
|
||||
json={"app_ids": excessive_ids},
|
||||
),
|
||||
pytest.raises(HTTPException) as exc,
|
||||
):
|
||||
args = workflow_module.WorkflowOnlineUsersPayload.model_validate({"app_ids": excessive_ids})
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
handler(api, args, "tenant-1", SimpleNamespace(id="account-1"))
|
||||
handler(api, args, "tenant-1", SimpleNamespace(id="account-1"))
|
||||
|
||||
assert exc.value.code == 400
|
||||
assert exc.value.description is not None
|
||||
|
||||
@@ -184,11 +184,13 @@ def test_write_endpoints_require_edit_permission(app: Flask, monkeypatch: pytest
|
||||
_patch_console_guards(monkeypatch, account, app_model)
|
||||
_patch_write_services(monkeypatch)
|
||||
|
||||
with app.test_request_context(case.path, method=case.method_name.upper(), json=case.payload):
|
||||
with _patch_payload(case.payload):
|
||||
handler = getattr(case.resource_cls(), case.method_name)
|
||||
with pytest.raises(Forbidden):
|
||||
handler(**case.kwargs)
|
||||
with (
|
||||
app.test_request_context(case.path, method=case.method_name.upper(), json=case.payload),
|
||||
_patch_payload(case.payload),
|
||||
):
|
||||
handler = getattr(case.resource_cls(), case.method_name)
|
||||
with pytest.raises(Forbidden):
|
||||
handler(**case.kwargs)
|
||||
|
||||
|
||||
def test_create_comment_allows_editor(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -206,9 +208,11 @@ def test_create_comment_allows_editor(app: Flask, monkeypatch: pytest.MonkeyPatc
|
||||
"mentioned_user_ids": [],
|
||||
}
|
||||
|
||||
with app.test_request_context("/console/api/apps/app-123/workflow/comments", method="POST", json=payload):
|
||||
with _patch_payload(payload):
|
||||
result = workflow_comment_module.WorkflowCommentListApi().post(app_id="app-123")
|
||||
with (
|
||||
app.test_request_context("/console/api/apps/app-123/workflow/comments", method="POST", json=payload),
|
||||
_patch_payload(payload),
|
||||
):
|
||||
result = workflow_comment_module.WorkflowCommentListApi().post(app_id="app-123")
|
||||
|
||||
response, status = unwrap_response(result)
|
||||
assert response["id"] == "comment-1"
|
||||
@@ -236,9 +240,11 @@ def test_update_comment_omits_mentions_when_payload_does_not_include_them(
|
||||
monkeypatch.setattr(workflow_comment_module.WorkflowCommentService, "update_comment", update_comment_mock)
|
||||
payload: dict[str, object] = {"content": "hello", "position_x": 10.0, "position_y": 20.0}
|
||||
|
||||
with app.test_request_context("/console/api/apps/app-123/workflow/comments/comment-1", method="PUT", json=payload):
|
||||
with _patch_payload(payload):
|
||||
result = workflow_comment_module.WorkflowCommentDetailApi().put(app_id="app-123", comment_id="comment-1")
|
||||
with (
|
||||
app.test_request_context("/console/api/apps/app-123/workflow/comments/comment-1", method="PUT", json=payload),
|
||||
_patch_payload(payload),
|
||||
):
|
||||
result = workflow_comment_module.WorkflowCommentDetailApi().put(app_id="app-123", comment_id="comment-1")
|
||||
|
||||
response, status = unwrap_response(result)
|
||||
assert response == {"id": "comment-1", "updated_at": JAN_1_2024_NOON_TS}
|
||||
@@ -493,9 +499,11 @@ def test_mutation_endpoints_serialize_response_models(
|
||||
MagicMock(return_value=case.service_return),
|
||||
)
|
||||
|
||||
with app.test_request_context(case.path, method=case.method_name.upper(), json=case.payload):
|
||||
with _patch_payload(case.payload):
|
||||
result = getattr(case.resource_cls(), case.method_name)(**case.kwargs)
|
||||
with (
|
||||
app.test_request_context(case.path, method=case.method_name.upper(), json=case.payload),
|
||||
_patch_payload(case.payload),
|
||||
):
|
||||
result = getattr(case.resource_cls(), case.method_name)(**case.kwargs)
|
||||
|
||||
response, status = unwrap_response(result)
|
||||
assert response == case.expected_response
|
||||
|
||||
@@ -226,13 +226,15 @@ def test_human_input_delivery_test_maps_validation_error(app: Flask, monkeypatch
|
||||
service_instance.test_human_input_delivery.side_effect = ValueError("bad delivery method")
|
||||
monkeypatch.setattr(workflow_module, "WorkflowService", MagicMock(return_value=service_instance))
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/apps/app-123/workflows/draft/human-input/nodes/node-1/delivery-test",
|
||||
method="POST",
|
||||
json={"delivery_method_id": "bad"},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/apps/app-123/workflows/draft/human-input/nodes/node-1/delivery-test",
|
||||
method="POST",
|
||||
json={"delivery_method_id": "bad"},
|
||||
),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
workflow_module.WorkflowDraftHumanInputDeliveryTestApi().post(app_id=app_model.id, node_id="node-1")
|
||||
workflow_module.WorkflowDraftHumanInputDeliveryTestApi().post(app_id=app_model.id, node_id="node-1")
|
||||
|
||||
|
||||
def test_human_input_preview_rejects_non_mapping(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -240,10 +242,12 @@ def test_human_input_preview_rejects_non_mapping(app: Flask, monkeypatch: pytest
|
||||
app_model = _make_app(AppMode.ADVANCED_CHAT)
|
||||
_patch_console_guards(monkeypatch, account, app_model)
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/apps/app-123/advanced-chat/workflows/draft/human-input/nodes/node-1/form/preview",
|
||||
method="POST",
|
||||
json={"inputs": ["not-a-dict"]},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/apps/app-123/advanced-chat/workflows/draft/human-input/nodes/node-1/form/preview",
|
||||
method="POST",
|
||||
json={"inputs": ["not-a-dict"]},
|
||||
),
|
||||
pytest.raises(UnprocessableEntity),
|
||||
):
|
||||
with pytest.raises(UnprocessableEntity):
|
||||
workflow_module.AdvancedChatDraftHumanInputFormPreviewApi().post(app_id=app_model.id, node_id="node-1")
|
||||
workflow_module.AdvancedChatDraftHumanInputFormPreviewApi().post(app_id=app_model.id, node_id="node-1")
|
||||
|
||||
@@ -155,13 +155,15 @@ def test_pause_details_tenant_isolation(app: Flask, monkeypatch: pytest.MonkeyPa
|
||||
)
|
||||
|
||||
handler = inspect.unwrap(workflow_run_module.ConsoleWorkflowPauseDetailsApi.get)
|
||||
with app.test_request_context(f"/console/api/workflow/{run_id}/pause-details", method="GET"):
|
||||
with pytest.raises(NotFoundError):
|
||||
handler(
|
||||
workflow_run_module.ConsoleWorkflowPauseDetailsApi(),
|
||||
str(uuid4()),
|
||||
workflow_run_id=run_id,
|
||||
)
|
||||
with (
|
||||
app.test_request_context(f"/console/api/workflow/{run_id}/pause-details", method="GET"),
|
||||
pytest.raises(NotFoundError),
|
||||
):
|
||||
handler(
|
||||
workflow_run_module.ConsoleWorkflowPauseDetailsApi(),
|
||||
str(uuid4()),
|
||||
workflow_run_id=run_id,
|
||||
)
|
||||
|
||||
|
||||
def test_pause_details_returns_empty_response_for_non_paused_run(
|
||||
|
||||
@@ -132,14 +132,16 @@ def test_send_email_translates_application_errors(
|
||||
service = _service()
|
||||
service.send_code.side_effect = service_error
|
||||
|
||||
with _request(
|
||||
app,
|
||||
service,
|
||||
path="/email-register/send-email",
|
||||
payload={"email": "invitee@example.com"},
|
||||
with (
|
||||
_request(
|
||||
app,
|
||||
service,
|
||||
path="/email-register/send-email",
|
||||
payload={"email": "invitee@example.com"},
|
||||
),
|
||||
pytest.raises(http_error),
|
||||
):
|
||||
with pytest.raises(http_error):
|
||||
EmailRegisterSendEmailApi().post()
|
||||
EmailRegisterSendEmailApi().post()
|
||||
|
||||
|
||||
def test_verify_email_code_serializes_application_result(app: Flask) -> None:
|
||||
@@ -182,14 +184,16 @@ def test_verify_email_code_translates_application_errors(
|
||||
service = _service()
|
||||
service.verify_code.side_effect = service_error
|
||||
|
||||
with _request(
|
||||
app,
|
||||
service,
|
||||
path="/email-register/validity",
|
||||
payload={"email": "user@example.com", "code": "wrong", "token": "pending-token"},
|
||||
with (
|
||||
_request(
|
||||
app,
|
||||
service,
|
||||
path="/email-register/validity",
|
||||
payload={"email": "user@example.com", "code": "wrong", "token": "pending-token"},
|
||||
),
|
||||
pytest.raises(http_error),
|
||||
):
|
||||
with pytest.raises(http_error):
|
||||
EmailRegisterCheckApi().post()
|
||||
EmailRegisterCheckApi().post()
|
||||
|
||||
|
||||
def test_register_delegates_and_serializes_tokens(app: Flask) -> None:
|
||||
@@ -252,18 +256,20 @@ def test_register_translates_application_errors(
|
||||
service = _service()
|
||||
service.register.side_effect = service_error
|
||||
|
||||
with _request(
|
||||
app,
|
||||
service,
|
||||
path="/email-register",
|
||||
payload={
|
||||
"token": "verified-token",
|
||||
"new_password": "ValidPass123!",
|
||||
"password_confirm": "ValidPass123!",
|
||||
},
|
||||
with (
|
||||
_request(
|
||||
app,
|
||||
service,
|
||||
path="/email-register",
|
||||
payload={
|
||||
"token": "verified-token",
|
||||
"new_password": "ValidPass123!",
|
||||
"password_confirm": "ValidPass123!",
|
||||
},
|
||||
),
|
||||
pytest.raises(http_error),
|
||||
):
|
||||
with pytest.raises(http_error):
|
||||
EmailRegisterResetApi().post()
|
||||
EmailRegisterResetApi().post()
|
||||
|
||||
|
||||
def test_reset_payload_rejects_invalid_timezone() -> None:
|
||||
|
||||
@@ -224,9 +224,9 @@ class TestEmailCodeLoginSendEmailApi:
|
||||
# Act & Assert
|
||||
with (
|
||||
app.test_request_context("/email-code-login", method="POST", json={"email": "test@example.com"}),
|
||||
pytest.raises(EmailSendIpLimitError),
|
||||
):
|
||||
with pytest.raises(EmailSendIpLimitError):
|
||||
EmailCodeLoginSendEmailApi().post()
|
||||
EmailCodeLoginSendEmailApi().post()
|
||||
|
||||
mock_verify.assert_not_called()
|
||||
|
||||
|
||||
@@ -67,13 +67,13 @@ class TestForgotPasswordSendEmailApi:
|
||||
"controllers.console.wraps.SystemFeatureService.is_email_password_login_enabled",
|
||||
return_value=wraps_features.enable_email_password_login,
|
||||
),
|
||||
):
|
||||
with app.test_request_context(
|
||||
app.test_request_context(
|
||||
"/forgot-password",
|
||||
method="POST",
|
||||
json={"email": "User@Example.com", "language": "zh-Hans"},
|
||||
):
|
||||
response = ForgotPasswordSendEmailApi().post()
|
||||
),
|
||||
):
|
||||
response = ForgotPasswordSendEmailApi().post()
|
||||
|
||||
assert response == {"result": "success", "data": "token-123"}
|
||||
mock_send_email.assert_called_once_with(
|
||||
@@ -117,13 +117,13 @@ class TestForgotPasswordCheckApi:
|
||||
"controllers.console.wraps.SystemFeatureService.is_email_password_login_enabled",
|
||||
return_value=wraps_features.enable_email_password_login,
|
||||
),
|
||||
):
|
||||
with app.test_request_context(
|
||||
app.test_request_context(
|
||||
"/forgot-password/validity",
|
||||
method="POST",
|
||||
json={"email": "ADMIN@Example.com", "code": "4321", "token": "token-123"},
|
||||
):
|
||||
response = ForgotPasswordCheckApi().post()
|
||||
),
|
||||
):
|
||||
response = ForgotPasswordCheckApi().post()
|
||||
|
||||
assert response == {"is_valid": True, "email": "admin@example.com", "token": "new-token"}
|
||||
mock_rate_limit_check.assert_called_once_with("admin@example.com")
|
||||
@@ -166,8 +166,7 @@ class TestForgotPasswordResetApi:
|
||||
"controllers.console.wraps.SystemFeatureService.is_email_password_login_enabled",
|
||||
return_value=wraps_features.enable_email_password_login,
|
||||
),
|
||||
):
|
||||
with database_app.test_request_context(
|
||||
database_app.test_request_context(
|
||||
"/forgot-password/resets",
|
||||
method="POST",
|
||||
json={
|
||||
@@ -175,8 +174,9 @@ class TestForgotPasswordResetApi:
|
||||
"new_password": "ValidPass123!",
|
||||
"password_confirm": "ValidPass123!",
|
||||
},
|
||||
):
|
||||
response = ForgotPasswordResetApi().post()
|
||||
),
|
||||
):
|
||||
response = ForgotPasswordResetApi().post()
|
||||
|
||||
assert response == {"result": "success"}
|
||||
mock_get_reset_data.assert_called_once_with("token-123")
|
||||
|
||||
@@ -287,13 +287,15 @@ class TestLoginApi:
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
mock_get_freeze_type.return_value = "email_domain_suspended"
|
||||
|
||||
with app.test_request_context(
|
||||
"/login",
|
||||
method="POST",
|
||||
json={"email": "user@suspended.example", "password": encode_password("password")},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/login",
|
||||
method="POST",
|
||||
json={"email": "user@suspended.example", "password": encode_password("password")},
|
||||
),
|
||||
pytest.raises(EmailDomainSuspendedError),
|
||||
):
|
||||
with pytest.raises(EmailDomainSuspendedError):
|
||||
LoginApi().post()
|
||||
LoginApi().post()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("service_error", "expected_error"),
|
||||
@@ -319,13 +321,15 @@ class TestLoginApi:
|
||||
)
|
||||
mock_get_account.side_effect = service_error
|
||||
|
||||
with app.test_request_context(
|
||||
"/email-code-login/validity",
|
||||
method="POST",
|
||||
json={"email": "User@Example.com", "code": encode_code("123456"), "token": TEST_TOKEN},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/email-code-login/validity",
|
||||
method="POST",
|
||||
json={"email": "User@Example.com", "code": encode_code("123456"), "token": TEST_TOKEN},
|
||||
),
|
||||
pytest.raises(expected_error),
|
||||
):
|
||||
with pytest.raises(expected_error):
|
||||
EmailCodeLoginApi().post()
|
||||
EmailCodeLoginApi().post()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("service_error", "expected_error"),
|
||||
@@ -357,13 +361,15 @@ class TestLoginApi:
|
||||
mock_get_account.return_value = None
|
||||
mock_create_account.side_effect = service_error
|
||||
|
||||
with app.test_request_context(
|
||||
"/email-code-login/validity",
|
||||
method="POST",
|
||||
json={"email": "User@Example.com", "code": encode_code("123456"), "token": TEST_TOKEN},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/email-code-login/validity",
|
||||
method="POST",
|
||||
json={"email": "User@Example.com", "code": encode_code("123456"), "token": TEST_TOKEN},
|
||||
),
|
||||
pytest.raises(expected_error),
|
||||
):
|
||||
with pytest.raises(expected_error):
|
||||
EmailCodeLoginApi().post()
|
||||
EmailCodeLoginApi().post()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("service_error", "expected_error"),
|
||||
@@ -384,13 +390,15 @@ class TestLoginApi:
|
||||
):
|
||||
mock_get_account.side_effect = service_error
|
||||
|
||||
with app.test_request_context(
|
||||
"/reset-password",
|
||||
method="POST",
|
||||
json={"email": "User@Example.com"},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/reset-password",
|
||||
method="POST",
|
||||
json={"email": "User@Example.com"},
|
||||
),
|
||||
pytest.raises(expected_error),
|
||||
):
|
||||
with pytest.raises(expected_error):
|
||||
ResetPasswordSendEmailApi().post()
|
||||
ResetPasswordSendEmailApi().post()
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.auth.login.AccountService.is_login_error_rate_limit")
|
||||
@@ -606,13 +614,15 @@ class TestLoginApi:
|
||||
)
|
||||
mock_get_account.side_effect = Unauthorized("Account is banned.")
|
||||
|
||||
with app.test_request_context(
|
||||
"/email-code-login/validity",
|
||||
method="POST",
|
||||
json={"email": "User@Example.com", "code": encode_code("123456"), "token": TEST_TOKEN},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/email-code-login/validity",
|
||||
method="POST",
|
||||
json={"email": "User@Example.com", "code": encode_code("123456"), "token": TEST_TOKEN},
|
||||
),
|
||||
pytest.raises(AccountBannedError),
|
||||
):
|
||||
with pytest.raises(AccountBannedError):
|
||||
EmailCodeLoginApi().post()
|
||||
EmailCodeLoginApi().post()
|
||||
|
||||
warn_records = [
|
||||
r for r in caplog.records if r.name == "controllers.console.auth.login" and r.levelno == logging.WARNING
|
||||
@@ -650,13 +660,15 @@ class TestLoginApi:
|
||||
mock_create_account.side_effect = SeatsLimitExceededError("licensed seats limit exceeded")
|
||||
|
||||
# Act & Assert
|
||||
with app.test_request_context(
|
||||
"/email-code-login/validity",
|
||||
method="POST",
|
||||
json={"email": "User@Example.com", "code": encode_code("123456"), "token": TEST_TOKEN},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/email-code-login/validity",
|
||||
method="POST",
|
||||
json={"email": "User@Example.com", "code": encode_code("123456"), "token": TEST_TOKEN},
|
||||
),
|
||||
pytest.raises(SeatsLimitExceeded),
|
||||
):
|
||||
with pytest.raises(SeatsLimitExceeded):
|
||||
EmailCodeLoginApi().post()
|
||||
EmailCodeLoginApi().post()
|
||||
|
||||
mock_create_account.assert_called_once()
|
||||
|
||||
|
||||
@@ -604,9 +604,8 @@ class TestAccountGeneration:
|
||||
mock_feature_service.is_registration_allowed.return_value = False
|
||||
mock_get_freeze_type.return_value = freeze_type
|
||||
|
||||
with app.test_request_context("/"):
|
||||
with pytest.raises(expected_error):
|
||||
_generate_account("github", user_info)
|
||||
with app.test_request_context("/"), pytest.raises(expected_error):
|
||||
_generate_account("github", user_info)
|
||||
|
||||
mock_get_freeze_type.assert_called_once_with("test@example.com")
|
||||
|
||||
|
||||
@@ -216,9 +216,9 @@ def test_account_endpoint_preserves_client_validation_before_bearer_challenge()
|
||||
with (
|
||||
app.test_request_context(json={"client_id": "missing"}),
|
||||
patch("controllers.console.auth.oauth_server.application_services", return_value=_services(service)),
|
||||
pytest.raises(NotFound, match="client_id is invalid"),
|
||||
):
|
||||
with pytest.raises(NotFound, match="client_id is invalid"):
|
||||
unwrap(OAuthServerUserAccountApi.post)(OAuthServerUserAccountApi())
|
||||
unwrap(OAuthServerUserAccountApi.post)(OAuthServerUserAccountApi())
|
||||
|
||||
|
||||
def test_oauth_provider_app_response_requires_auto_authorize() -> None:
|
||||
|
||||
@@ -124,8 +124,7 @@ def test_generate_account_rejects_new_user_when_registration_disabled(
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY)
|
||||
user_info = OAuthUserInfo(id="github-123", name="Test User", email="user@example.com")
|
||||
|
||||
with app.test_request_context(headers={"Accept-Language": "en-US,en;q=0.9"}):
|
||||
with pytest.raises(AccountRegisterError):
|
||||
_generate_account("github", user_info)
|
||||
with app.test_request_context(headers={"Accept-Language": "en-US,en;q=0.9"}), pytest.raises(AccountRegisterError):
|
||||
_generate_account("github", user_info)
|
||||
|
||||
mock_register_service.register.assert_not_called()
|
||||
|
||||
@@ -102,9 +102,8 @@ class TestBillingPortal:
|
||||
method = unwrap(resource.get)
|
||||
billing_portal.get_invoices.side_effect = BillingUpstreamUnavailableError
|
||||
|
||||
with app.test_request_context("/billing/invoices"):
|
||||
with pytest.raises(BillingUnavailableError) as exc_info:
|
||||
method(resource, request_context)
|
||||
with app.test_request_context("/billing/invoices"), pytest.raises(BillingUnavailableError) as exc_info:
|
||||
method(resource, request_context)
|
||||
|
||||
assert exc_info.value.data == {
|
||||
"code": "billing_unavailable",
|
||||
@@ -123,9 +122,8 @@ class TestBillingPortal:
|
||||
query = SubscriptionQuery(plan=CloudPlan.PROFESSIONAL, interval="month")
|
||||
billing_portal.get_subscription.side_effect = BillingUpstreamInvalidResponseError
|
||||
|
||||
with app.test_request_context("/billing/subscription"):
|
||||
with pytest.raises(BillingOperationFailedError) as exc_info:
|
||||
method(resource, query, request_context)
|
||||
with app.test_request_context("/billing/subscription"), pytest.raises(BillingOperationFailedError) as exc_info:
|
||||
method(resource, query, request_context)
|
||||
|
||||
assert exc_info.value.data == {
|
||||
"code": "billing_operation_failed",
|
||||
@@ -205,20 +203,20 @@ class TestPartnerTenants:
|
||||
|
||||
partner_tenant_bindings.sync.return_value = expected_response
|
||||
|
||||
with app.test_request_context(
|
||||
method="PUT",
|
||||
json={"click_id": click_id},
|
||||
path=f"/billing/partners/{partner_key_encoded}/tenants",
|
||||
with (
|
||||
app.test_request_context(
|
||||
method="PUT",
|
||||
json={"click_id": click_id},
|
||||
path=f"/billing/partners/{partner_key_encoded}/tenants",
|
||||
),
|
||||
patch(
|
||||
"controllers.console.wraps.current_account_with_tenant",
|
||||
return_value=(mock_account, mock_account.current_tenant_id),
|
||||
),
|
||||
patch("libs.login._get_user", return_value=mock_account),
|
||||
):
|
||||
with (
|
||||
patch(
|
||||
"controllers.console.wraps.current_account_with_tenant",
|
||||
return_value=(mock_account, mock_account.current_tenant_id),
|
||||
),
|
||||
patch("libs.login._get_user", return_value=mock_account),
|
||||
):
|
||||
resource = PartnerTenants()
|
||||
result = resource.put(partner_key_encoded)
|
||||
resource = PartnerTenants()
|
||||
result = resource.put(partner_key_encoded)
|
||||
|
||||
# Assert
|
||||
assert result == expected_response
|
||||
@@ -234,48 +232,48 @@ class TestPartnerTenants:
|
||||
invalid_partner_key = "invalid-base64-!@#$"
|
||||
click_id = "click-id-789"
|
||||
|
||||
with app.test_request_context(
|
||||
method="PUT",
|
||||
json={"click_id": click_id},
|
||||
path=f"/billing/partners/{invalid_partner_key}/tenants",
|
||||
with (
|
||||
app.test_request_context(
|
||||
method="PUT",
|
||||
json={"click_id": click_id},
|
||||
path=f"/billing/partners/{invalid_partner_key}/tenants",
|
||||
),
|
||||
patch(
|
||||
"controllers.console.wraps.current_account_with_tenant",
|
||||
return_value=(mock_account, mock_account.current_tenant_id),
|
||||
),
|
||||
patch("libs.login._get_user", return_value=mock_account),
|
||||
):
|
||||
with (
|
||||
patch(
|
||||
"controllers.console.wraps.current_account_with_tenant",
|
||||
return_value=(mock_account, mock_account.current_tenant_id),
|
||||
),
|
||||
patch("libs.login._get_user", return_value=mock_account),
|
||||
):
|
||||
resource = PartnerTenants()
|
||||
resource = PartnerTenants()
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(BadRequest) as exc_info:
|
||||
resource.put(invalid_partner_key)
|
||||
assert "Invalid partner_key" in str(exc_info.value)
|
||||
# Act & Assert
|
||||
with pytest.raises(BadRequest) as exc_info:
|
||||
resource.put(invalid_partner_key)
|
||||
assert "Invalid partner_key" in str(exc_info.value)
|
||||
|
||||
def test_put_missing_click_id(self, app: Flask, mock_account, partner_tenant_bindings, mock_decorators):
|
||||
"""Test that missing click_id raises UnprocessableEntity (422)."""
|
||||
# Arrange
|
||||
partner_key_encoded = base64.b64encode(b"partner-key-123").decode("utf-8")
|
||||
|
||||
with app.test_request_context(
|
||||
method="PUT",
|
||||
json={},
|
||||
path=f"/billing/partners/{partner_key_encoded}/tenants",
|
||||
with (
|
||||
app.test_request_context(
|
||||
method="PUT",
|
||||
json={},
|
||||
path=f"/billing/partners/{partner_key_encoded}/tenants",
|
||||
),
|
||||
patch(
|
||||
"controllers.console.wraps.current_account_with_tenant",
|
||||
return_value=(mock_account, mock_account.current_tenant_id),
|
||||
),
|
||||
patch("libs.login._get_user", return_value=mock_account),
|
||||
):
|
||||
with (
|
||||
patch(
|
||||
"controllers.console.wraps.current_account_with_tenant",
|
||||
return_value=(mock_account, mock_account.current_tenant_id),
|
||||
),
|
||||
patch("libs.login._get_user", return_value=mock_account),
|
||||
):
|
||||
resource = PartnerTenants()
|
||||
resource = PartnerTenants()
|
||||
|
||||
# Act & Assert
|
||||
# Validation should raise UnprocessableEntity (422) for missing required field
|
||||
with pytest.raises(UnprocessableEntity):
|
||||
resource.put(partner_key_encoded)
|
||||
# Act & Assert
|
||||
# Validation should raise UnprocessableEntity (422) for missing required field
|
||||
with pytest.raises(UnprocessableEntity):
|
||||
resource.put(partner_key_encoded)
|
||||
|
||||
def test_put_billing_service_json_decode_error(
|
||||
self, app: Flask, mock_account, partner_tenant_bindings, mock_decorators
|
||||
@@ -300,30 +298,30 @@ class TestPartnerTenants:
|
||||
json_decode_error = json.JSONDecodeError("Expecting value", "", 0)
|
||||
partner_tenant_bindings.sync.side_effect = json_decode_error
|
||||
|
||||
with app.test_request_context(
|
||||
method="PUT",
|
||||
json={"click_id": click_id},
|
||||
path=f"/billing/partners/{partner_key_encoded}/tenants",
|
||||
with (
|
||||
app.test_request_context(
|
||||
method="PUT",
|
||||
json={"click_id": click_id},
|
||||
path=f"/billing/partners/{partner_key_encoded}/tenants",
|
||||
),
|
||||
patch(
|
||||
"controllers.console.wraps.current_account_with_tenant",
|
||||
return_value=(mock_account, mock_account.current_tenant_id),
|
||||
),
|
||||
patch("libs.login._get_user", return_value=mock_account),
|
||||
):
|
||||
with (
|
||||
patch(
|
||||
"controllers.console.wraps.current_account_with_tenant",
|
||||
return_value=(mock_account, mock_account.current_tenant_id),
|
||||
),
|
||||
patch("libs.login._get_user", return_value=mock_account),
|
||||
):
|
||||
resource = PartnerTenants()
|
||||
resource = PartnerTenants()
|
||||
|
||||
# Act & Assert
|
||||
# JSONDecodeError will be raised from the controller
|
||||
# In actual Flask app, this would be caught by handle_general_exception
|
||||
# which returns: {"code": "unknown", "message": str(e), "status": 500}
|
||||
with pytest.raises(json.JSONDecodeError) as exc_info:
|
||||
resource.put(partner_key_encoded)
|
||||
# Act & Assert
|
||||
# JSONDecodeError will be raised from the controller
|
||||
# In actual Flask app, this would be caught by handle_general_exception
|
||||
# which returns: {"code": "unknown", "message": str(e), "status": 500}
|
||||
with pytest.raises(json.JSONDecodeError) as exc_info:
|
||||
resource.put(partner_key_encoded)
|
||||
|
||||
# Verify the exception is JSONDecodeError
|
||||
assert isinstance(exc_info.value, json.JSONDecodeError)
|
||||
assert "Expecting value" in str(exc_info.value)
|
||||
# Verify the exception is JSONDecodeError
|
||||
assert isinstance(exc_info.value, json.JSONDecodeError)
|
||||
assert "Expecting value" in str(exc_info.value)
|
||||
|
||||
def test_put_empty_click_id(self, app: Flask, mock_account, partner_tenant_bindings, mock_decorators):
|
||||
"""Test that empty click_id raises BadRequest."""
|
||||
@@ -331,24 +329,24 @@ class TestPartnerTenants:
|
||||
partner_key_encoded = base64.b64encode(b"partner-key-123").decode("utf-8")
|
||||
click_id = ""
|
||||
|
||||
with app.test_request_context(
|
||||
method="PUT",
|
||||
json={"click_id": click_id},
|
||||
path=f"/billing/partners/{partner_key_encoded}/tenants",
|
||||
with (
|
||||
app.test_request_context(
|
||||
method="PUT",
|
||||
json={"click_id": click_id},
|
||||
path=f"/billing/partners/{partner_key_encoded}/tenants",
|
||||
),
|
||||
patch(
|
||||
"controllers.console.wraps.current_account_with_tenant",
|
||||
return_value=(mock_account, mock_account.current_tenant_id),
|
||||
),
|
||||
patch("libs.login._get_user", return_value=mock_account),
|
||||
):
|
||||
with (
|
||||
patch(
|
||||
"controllers.console.wraps.current_account_with_tenant",
|
||||
return_value=(mock_account, mock_account.current_tenant_id),
|
||||
),
|
||||
patch("libs.login._get_user", return_value=mock_account),
|
||||
):
|
||||
resource = PartnerTenants()
|
||||
resource = PartnerTenants()
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(BadRequest) as exc_info:
|
||||
resource.put(partner_key_encoded)
|
||||
assert "Invalid partner information" in str(exc_info.value)
|
||||
# Act & Assert
|
||||
with pytest.raises(BadRequest) as exc_info:
|
||||
resource.put(partner_key_encoded)
|
||||
assert "Invalid partner information" in str(exc_info.value)
|
||||
|
||||
def test_put_empty_partner_key_after_decode(
|
||||
self, app: Flask, mock_account, partner_tenant_bindings, mock_decorators
|
||||
@@ -359,21 +357,21 @@ class TestPartnerTenants:
|
||||
empty_partner_key_encoded = base64.b64encode(b"").decode("utf-8")
|
||||
click_id = "click-id-789"
|
||||
|
||||
with app.test_request_context(
|
||||
method="PUT",
|
||||
json={"click_id": click_id},
|
||||
path=f"/billing/partners/{empty_partner_key_encoded}/tenants",
|
||||
with (
|
||||
app.test_request_context(
|
||||
method="PUT",
|
||||
json={"click_id": click_id},
|
||||
path=f"/billing/partners/{empty_partner_key_encoded}/tenants",
|
||||
),
|
||||
patch(
|
||||
"controllers.console.wraps.current_account_with_tenant",
|
||||
return_value=(mock_account, mock_account.current_tenant_id),
|
||||
),
|
||||
patch("libs.login._get_user", return_value=mock_account),
|
||||
):
|
||||
with (
|
||||
patch(
|
||||
"controllers.console.wraps.current_account_with_tenant",
|
||||
return_value=(mock_account, mock_account.current_tenant_id),
|
||||
),
|
||||
patch("libs.login._get_user", return_value=mock_account),
|
||||
):
|
||||
resource = PartnerTenants()
|
||||
resource = PartnerTenants()
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(BadRequest) as exc_info:
|
||||
resource.put(empty_partner_key_encoded)
|
||||
assert "Invalid partner information" in str(exc_info.value)
|
||||
# Act & Assert
|
||||
with pytest.raises(BadRequest) as exc_info:
|
||||
resource.put(empty_partner_key_encoded)
|
||||
assert "Invalid partner information" in str(exc_info.value)
|
||||
|
||||
@@ -81,9 +81,9 @@ def test_get_compliance_download_translates_rate_limit(
|
||||
with (
|
||||
app.test_request_context("/compliance/download"),
|
||||
patch("controllers.console.billing.compliance.extract_remote_ip", return_value="127.0.0.1"),
|
||||
pytest.raises(ComplianceRateLimitError) as exc_info,
|
||||
):
|
||||
with pytest.raises(ComplianceRateLimitError) as exc_info:
|
||||
method(resource, query, request_context)
|
||||
method(resource, query, request_context)
|
||||
|
||||
assert exc_info.value.data == {
|
||||
"code": "compliance_rate_limit",
|
||||
|
||||
@@ -172,9 +172,9 @@ class TestDatasourcePluginOAuthAuthorizationUrl:
|
||||
"get_oauth_client",
|
||||
return_value=None,
|
||||
),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, "tenant-1", user, "notion")
|
||||
method(api, "tenant-1", user, "notion")
|
||||
|
||||
def test_get_without_credential_id_sets_cookie(self, app: Flask):
|
||||
api = DatasourcePluginOAuthAuthorizationUrl()
|
||||
@@ -271,9 +271,8 @@ class TestDatasourceOAuthCallback:
|
||||
api = DatasourceOAuthCallback()
|
||||
method = inspect.unwrap(api.get)
|
||||
|
||||
with app.test_request_context("/"):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, "notion")
|
||||
with app.test_request_context("/"), pytest.raises(Forbidden):
|
||||
method(api, "notion")
|
||||
|
||||
def test_callback_invalid_context(self, app: Flask):
|
||||
api = DatasourceOAuthCallback()
|
||||
@@ -286,9 +285,9 @@ class TestDatasourceOAuthCallback:
|
||||
"use_proxy_context",
|
||||
return_value=None,
|
||||
),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, "notion")
|
||||
method(api, "notion")
|
||||
|
||||
def test_callback_oauth_config_not_found(self, app: Flask):
|
||||
api = DatasourceOAuthCallback()
|
||||
@@ -308,9 +307,9 @@ class TestDatasourceOAuthCallback:
|
||||
"get_oauth_client",
|
||||
return_value=None,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, "notion")
|
||||
method(api, "notion")
|
||||
|
||||
def test_callback_reauthorize_existing_credential(self, app: Flask):
|
||||
api = DatasourceOAuthCallback()
|
||||
@@ -450,9 +449,9 @@ class TestDatasourceAuth:
|
||||
"add_datasource_api_key_provider",
|
||||
side_effect=CredentialsValidateFailedError("invalid"),
|
||||
),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, DatasourceCredentialPayload.model_validate(payload), "tenant-1", "notion")
|
||||
method(api, DatasourceCredentialPayload.model_validate(payload), "tenant-1", "notion")
|
||||
|
||||
def test_get_success(self, app: Flask):
|
||||
api = DatasourceAuth()
|
||||
@@ -481,9 +480,9 @@ class TestDatasourceAuth:
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, DatasourceCredentialPayload.model_validate(payload), "tenant-1", "notion")
|
||||
method(api, DatasourceCredentialPayload.model_validate(payload), "tenant-1", "notion")
|
||||
|
||||
def test_get_empty_list(self, app: Flask):
|
||||
api = DatasourceAuth()
|
||||
@@ -542,9 +541,9 @@ class TestDatasourceAuthDeleteApi:
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, DatasourceCredentialDeletePayload.model_validate(payload), "tenant-1", "notion")
|
||||
method(api, DatasourceCredentialDeletePayload.model_validate(payload), "tenant-1", "notion")
|
||||
|
||||
|
||||
class TestDatasourceAuthUpdateApi:
|
||||
@@ -864,9 +863,9 @@ class TestDatasourceAuthDefaultApi:
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, DatasourceDefaultPayload.model_validate(payload), "tenant-1", "notion")
|
||||
method(api, DatasourceDefaultPayload.model_validate(payload), "tenant-1", "notion")
|
||||
|
||||
|
||||
class TestDatasourceUpdateProviderNameApi:
|
||||
@@ -908,9 +907,9 @@ class TestDatasourceUpdateProviderNameApi:
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, DatasourceUpdateNamePayload.model_validate(payload), "tenant-1", "notion")
|
||||
method(api, DatasourceUpdateNamePayload.model_validate(payload), "tenant-1", "notion")
|
||||
|
||||
def test_update_name_missing_credential_id(self, app: Flask):
|
||||
api = DatasourceUpdateProviderNameApi()
|
||||
@@ -921,6 +920,6 @@ class TestDatasourceUpdateProviderNameApi:
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, DatasourceUpdateNamePayload.model_validate(payload), "tenant-1", "notion")
|
||||
method(api, DatasourceUpdateNamePayload.model_validate(payload), "tenant-1", "notion")
|
||||
|
||||
@@ -80,9 +80,9 @@ class TestDataSourceContentPreviewApi:
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, Parser.model_validate(payload), account, pipeline, "node-1")
|
||||
method(api, Parser.model_validate(payload), account, pipeline, "node-1")
|
||||
|
||||
def test_post_without_credential_id(self, app: Flask):
|
||||
api = DataSourceContentPreviewApi()
|
||||
|
||||
@@ -81,9 +81,9 @@ class TestCreateRagPipelineDatasetApi:
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, RagPipelineDatasetImportPayload.model_validate(payload), "tenant-1", user)
|
||||
method(api, RagPipelineDatasetImportPayload.model_validate(payload), "tenant-1", user)
|
||||
|
||||
def test_post_dataset_name_duplicate(self, app: Flask) -> None:
|
||||
api = CreateRagPipelineDatasetApi()
|
||||
@@ -102,9 +102,9 @@ class TestCreateRagPipelineDatasetApi:
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline_datasets.RagPipelineDslService",
|
||||
return_value=mock_service,
|
||||
),
|
||||
pytest.raises(DatasetNameDuplicateError),
|
||||
):
|
||||
with pytest.raises(DatasetNameDuplicateError):
|
||||
method(api, RagPipelineDatasetImportPayload.model_validate(payload), "tenant-1", user)
|
||||
method(api, RagPipelineDatasetImportPayload.model_validate(payload), "tenant-1", user)
|
||||
|
||||
def test_post_invalid_payload(self, app: Flask) -> None:
|
||||
api = CreateRagPipelineDatasetApi()
|
||||
@@ -116,9 +116,9 @@ class TestCreateRagPipelineDatasetApi:
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, RagPipelineDatasetImportPayload.model_validate(payload), "tenant-1", user)
|
||||
method(api, RagPipelineDatasetImportPayload.model_validate(payload), "tenant-1", user)
|
||||
|
||||
|
||||
class TestCreateEmptyRagPipelineDatasetApi:
|
||||
@@ -128,6 +128,5 @@ class TestCreateEmptyRagPipelineDatasetApi:
|
||||
|
||||
user = _account(editor=False)
|
||||
|
||||
with app.test_request_context("/"):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, "tenant-1", user)
|
||||
with app.test_request_context("/"), pytest.raises(Forbidden):
|
||||
method(api, "tenant-1", user)
|
||||
|
||||
@@ -166,9 +166,9 @@ class TestRagPipelineVariableCollectionApi:
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline_draft_variable.RagPipelineService",
|
||||
return_value=rag_srv,
|
||||
),
|
||||
pytest.raises(DraftWorkflowNotExist),
|
||||
):
|
||||
with pytest.raises(DraftWorkflowNotExist):
|
||||
method(api, PaginationQuery(), editor_user, pipeline)
|
||||
method(api, PaginationQuery(), editor_user, pipeline)
|
||||
|
||||
def test_delete_variables_success(self, app: Flask, sqlite_db, editor_user):
|
||||
api = RagPipelineVariableCollectionApi()
|
||||
@@ -218,11 +218,8 @@ class TestRagPipelineNodeVariableCollectionApi:
|
||||
api = RagPipelineNodeVariableCollectionApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
):
|
||||
with pytest.raises(InvalidArgumentError):
|
||||
method(api, editor_user, _pipeline(), SYSTEM_VARIABLE_NODE_ID)
|
||||
with app.test_request_context("/"), pytest.raises(InvalidArgumentError):
|
||||
method(api, editor_user, _pipeline(), SYSTEM_VARIABLE_NODE_ID)
|
||||
|
||||
|
||||
class TestRagPipelineVariableApi:
|
||||
@@ -240,9 +237,9 @@ class TestRagPipelineVariableApi:
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline_draft_variable.WorkflowDraftVariableService",
|
||||
return_value=srv,
|
||||
),
|
||||
pytest.raises(NotFoundError),
|
||||
):
|
||||
with pytest.raises(NotFoundError):
|
||||
method(api, editor_user, _pipeline(), "v1")
|
||||
method(api, editor_user, _pipeline(), "v1")
|
||||
|
||||
def test_patch_variable_invalid_file_payload(self, app: Flask, sqlite_db, editor_user):
|
||||
api = RagPipelineVariableApi()
|
||||
@@ -264,9 +261,9 @@ class TestRagPipelineVariableApi:
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline_draft_variable.WorkflowDraftVariableService",
|
||||
return_value=srv,
|
||||
),
|
||||
pytest.raises(InvalidArgumentError),
|
||||
):
|
||||
with pytest.raises(InvalidArgumentError):
|
||||
method(api, WorkflowDraftVariablePatchPayload.model_validate(payload), editor_user, pipeline, "v1")
|
||||
method(api, WorkflowDraftVariablePatchPayload.model_validate(payload), editor_user, pipeline, "v1")
|
||||
|
||||
def test_delete_variable_success(self, app: Flask, sqlite_db, editor_user):
|
||||
api = RagPipelineVariableApi()
|
||||
|
||||
@@ -298,9 +298,9 @@ class TestDraftWorkflowApi:
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline_workflow.RagPipelineService",
|
||||
return_value=service,
|
||||
),
|
||||
pytest.raises(DraftWorkflowNotExist),
|
||||
):
|
||||
with pytest.raises(DraftWorkflowNotExist):
|
||||
method(api, pipeline)
|
||||
method(api, pipeline)
|
||||
|
||||
def test_sync_hash_not_match(self, app: Flask) -> None:
|
||||
api = DraftRagPipelineApi()
|
||||
@@ -318,9 +318,9 @@ class TestDraftWorkflowApi:
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline_workflow.RagPipelineService",
|
||||
return_value=service,
|
||||
),
|
||||
pytest.raises(DraftWorkflowNotSync),
|
||||
):
|
||||
with pytest.raises(DraftWorkflowNotSync):
|
||||
method(api, user, pipeline)
|
||||
method(api, user, pipeline)
|
||||
|
||||
def test_sync_invalid_text_plain(self, app: Flask) -> None:
|
||||
api = DraftRagPipelineApi()
|
||||
@@ -377,9 +377,9 @@ class TestDraftWorkflowApi:
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline_workflow.RagPipelineService",
|
||||
return_value=service,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, user, pipeline, "published-workflow")
|
||||
method(api, user, pipeline, "published-workflow")
|
||||
|
||||
def test_restore_published_workflow_to_draft_returns_400_for_draft_source(self, app: Flask) -> None:
|
||||
api = RagPipelineDraftWorkflowRestoreApi()
|
||||
@@ -399,9 +399,9 @@ class TestDraftWorkflowApi:
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline_workflow.RagPipelineService",
|
||||
return_value=service,
|
||||
),
|
||||
pytest.raises(HTTPException) as exc,
|
||||
):
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
method(api, user, pipeline, "draft-workflow")
|
||||
method(api, user, pipeline, "draft-workflow")
|
||||
|
||||
assert exc.value.code == 400
|
||||
assert exc.value.description == "source workflow must be published"
|
||||
@@ -442,9 +442,9 @@ class TestDraftRunNodes:
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline_workflow.PipelineGenerateService.generate_single_iteration",
|
||||
side_effect=services.errors.conversation.ConversationNotExistsError(),
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, NodeRunPayload(), user, pipeline, "node")
|
||||
method(api, NodeRunPayload(), user, pipeline, "node")
|
||||
|
||||
def test_loop_node_success(self, app: Flask) -> None:
|
||||
api = RagPipelineDraftRunLoopNodeApi()
|
||||
@@ -484,9 +484,9 @@ class TestDraftNodeRun:
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline_workflow.RagPipelineService",
|
||||
return_value=service,
|
||||
),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, NodeRunRequiredPayload(inputs={}), user, pipeline, "node")
|
||||
method(api, NodeRunRequiredPayload(inputs={}), user, pipeline, "node")
|
||||
|
||||
|
||||
class TestPublishedPipelineApis:
|
||||
@@ -590,9 +590,8 @@ class TestDefaultBlockConfigApi:
|
||||
|
||||
pipeline = make_pipeline()
|
||||
|
||||
with app.test_request_context("/?q=bad-json"):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, DefaultBlockConfigQuery(q="bad-json"), pipeline, "llm")
|
||||
with app.test_request_context("/?q=bad-json"), pytest.raises(ValueError):
|
||||
method(api, DefaultBlockConfigQuery(q="bad-json"), pipeline, "llm")
|
||||
|
||||
|
||||
class TestPublishedAllRagPipelineApi:
|
||||
@@ -626,11 +625,8 @@ class TestPublishedAllRagPipelineApi:
|
||||
pipeline = make_pipeline()
|
||||
user = make_account(id="u1")
|
||||
|
||||
with (
|
||||
app.test_request_context("/?user_id=u2"),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, WorkflowListQuery(user_id="u2"), user, pipeline)
|
||||
with app.test_request_context("/?user_id=u2"), pytest.raises(Forbidden):
|
||||
method(api, WorkflowListQuery(user_id="u2"), user, pipeline)
|
||||
|
||||
|
||||
class TestRagPipelineByIdApi:
|
||||
@@ -742,9 +738,11 @@ class TestRagPipelineByIdApi:
|
||||
pipeline = make_pipeline(tenant_id="t1", workflow_id="active-workflow")
|
||||
user = make_account()
|
||||
|
||||
with app.test_request_context("/", method="DELETE"):
|
||||
with pytest.raises(BadRequest, match="currently in use by pipeline"):
|
||||
method(api, user, pipeline, "active-workflow")
|
||||
with (
|
||||
app.test_request_context("/", method="DELETE"),
|
||||
pytest.raises(BadRequest, match="currently in use by pipeline"),
|
||||
):
|
||||
method(api, user, pipeline, "active-workflow")
|
||||
|
||||
|
||||
class TestRagPipelineWorkflowLastRunApi:
|
||||
@@ -787,9 +785,9 @@ class TestRagPipelineWorkflowLastRunApi:
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline_workflow.RagPipelineService",
|
||||
return_value=service,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, pipeline, "node1")
|
||||
method(api, pipeline, "node1")
|
||||
|
||||
|
||||
class TestRagPipelineWorkflowRunNodeExecutionListApi:
|
||||
|
||||
@@ -142,9 +142,9 @@ class TestDataSourceNotionDatasetSyncApi:
|
||||
"controllers.console.datasets.data_source.DatasetService.get_dataset",
|
||||
return_value=None,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, sqlite_session, "ds-1")
|
||||
method(api, sqlite_session, "ds-1")
|
||||
|
||||
|
||||
class TestDataSourceNotionDocumentSyncApi:
|
||||
@@ -187,6 +187,6 @@ class TestDataSourceNotionDocumentSyncApi:
|
||||
"controllers.console.datasets.data_source.DocumentService.get_document",
|
||||
return_value=None,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, sqlite_session, "ds-1", "doc-1")
|
||||
method(api, sqlite_session, "ds-1", "doc-1")
|
||||
|
||||
@@ -203,12 +203,12 @@ class TestDatasetList(_UsesSQLiteSession):
|
||||
method = unwrap(api.get)
|
||||
current_user = self._mock_user()
|
||||
datasets = [make_dataset(icon_info={"icon": "📙", "icon_type": "emoji"})]
|
||||
with app.test_request_context("/datasets"):
|
||||
with (
|
||||
patch.object(DatasetService, "get_datasets", return_value=(datasets, 1)),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
resp, status = method(api, self.session, "tenant-1", current_user)
|
||||
with (
|
||||
app.test_request_context("/datasets"),
|
||||
patch.object(DatasetService, "get_datasets", return_value=(datasets, 1)),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
resp, status = method(api, self.session, "tenant-1", current_user)
|
||||
assert status == 200
|
||||
assert resp["total"] == 1
|
||||
assert resp["data"][0]["embedding_available"] is True
|
||||
@@ -225,12 +225,12 @@ class TestDatasetList(_UsesSQLiteSession):
|
||||
current_user = self._mock_user()
|
||||
dataset = make_dataset()
|
||||
session = self.session
|
||||
with app.test_request_context("/datasets"):
|
||||
with (
|
||||
patch.object(DatasetService, "get_datasets", return_value=([dataset], 1)),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
method(api, session, "tenant-1", current_user)
|
||||
with (
|
||||
app.test_request_context("/datasets"),
|
||||
patch.object(DatasetService, "get_datasets", return_value=([dataset], 1)),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
method(api, session, "tenant-1", current_user)
|
||||
|
||||
for getter in dataset_model_property_defaults.values():
|
||||
getter.assert_called_once_with(dataset, session=session)
|
||||
@@ -240,12 +240,12 @@ class TestDatasetList(_UsesSQLiteSession):
|
||||
method = unwrap(api.get)
|
||||
current_user = self._mock_user()
|
||||
datasets = [make_dataset()]
|
||||
with app.test_request_context("/datasets?ids=1&ids=2"):
|
||||
with (
|
||||
patch.object(DatasetService, "get_datasets_by_ids", return_value=(datasets, 2)) as by_ids_mock,
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
resp, status = method(api, self.session, "tenant-1", current_user)
|
||||
with (
|
||||
app.test_request_context("/datasets?ids=1&ids=2"),
|
||||
patch.object(DatasetService, "get_datasets_by_ids", return_value=(datasets, 2)) as by_ids_mock,
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
resp, status = method(api, self.session, "tenant-1", current_user)
|
||||
by_ids_mock.assert_called_once()
|
||||
assert status == 200
|
||||
assert resp["total"] == 2
|
||||
@@ -265,16 +265,16 @@ class TestDatasetList(_UsesSQLiteSession):
|
||||
],
|
||||
)
|
||||
)
|
||||
with app.test_request_context("/datasets"):
|
||||
with (
|
||||
patch.object(DatasetService, "get_datasets", return_value=([dataset], 1)),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.MyPermissions.get",
|
||||
return_value=permissions,
|
||||
) as get_permissions,
|
||||
):
|
||||
resp, status = method(api, self.session, "tenant-1", current_user)
|
||||
with (
|
||||
app.test_request_context("/datasets"),
|
||||
patch.object(DatasetService, "get_datasets", return_value=([dataset], 1)),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.MyPermissions.get",
|
||||
return_value=permissions,
|
||||
) as get_permissions,
|
||||
):
|
||||
resp, status = method(api, self.session, "tenant-1", current_user)
|
||||
get_permissions.assert_called_once_with("tenant-1", current_user.id, session=ANY)
|
||||
assert status == 200
|
||||
assert resp["data"][0]["permission_keys"] == ["dataset.acl.readonly", "dataset.acl.edit"]
|
||||
@@ -291,20 +291,20 @@ class TestDatasetList(_UsesSQLiteSession):
|
||||
permission_keys=["dataset.create_and_management"]
|
||||
)
|
||||
)
|
||||
with app.test_request_context("/datasets"):
|
||||
with (
|
||||
patch.object(DatasetService, "get_datasets", return_value=([], 0)) as get_datasets,
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.MyPermissions.get",
|
||||
return_value=permissions,
|
||||
),
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.DatasetAccess.whitelist_resources",
|
||||
return_value=SimpleNamespace(resource_ids=[]),
|
||||
),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
method(api, self.session, "tenant-1", current_user)
|
||||
with (
|
||||
app.test_request_context("/datasets"),
|
||||
patch.object(DatasetService, "get_datasets", return_value=([], 0)) as get_datasets,
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.MyPermissions.get",
|
||||
return_value=permissions,
|
||||
),
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.DatasetAccess.whitelist_resources",
|
||||
return_value=SimpleNamespace(resource_ids=[]),
|
||||
),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
method(api, self.session, "tenant-1", current_user)
|
||||
assert get_datasets.call_args.kwargs["accessible_dataset_ids"] == []
|
||||
assert get_datasets.call_args.kwargs["include_own_datasets"] is False
|
||||
|
||||
@@ -318,20 +318,20 @@ class TestDatasetList(_UsesSQLiteSession):
|
||||
permissions = enterprise_rbac_service.MyPermissionsResponse(
|
||||
dataset=enterprise_rbac_service.ResourcePermissionSnapshot(default_permission_keys=["dataset.preview"])
|
||||
)
|
||||
with app.test_request_context("/datasets"):
|
||||
with (
|
||||
patch.object(DatasetService, "get_datasets", return_value=([], 0)) as get_datasets,
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.MyPermissions.get",
|
||||
return_value=permissions,
|
||||
),
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.DatasetAccess.whitelist_resources",
|
||||
return_value=SimpleNamespace(unrestricted=True, resource_ids=[]),
|
||||
),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
method(api, self.session, "tenant-1", current_user)
|
||||
with (
|
||||
app.test_request_context("/datasets"),
|
||||
patch.object(DatasetService, "get_datasets", return_value=([], 0)) as get_datasets,
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.MyPermissions.get",
|
||||
return_value=permissions,
|
||||
),
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.DatasetAccess.whitelist_resources",
|
||||
return_value=SimpleNamespace(unrestricted=True, resource_ids=[]),
|
||||
),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
method(api, self.session, "tenant-1", current_user)
|
||||
assert get_datasets.call_args.kwargs["accessible_dataset_ids"] is None
|
||||
|
||||
def test_get_restricted_whitelist_overrides_default_read_permission(
|
||||
@@ -344,20 +344,20 @@ class TestDatasetList(_UsesSQLiteSession):
|
||||
permissions = enterprise_rbac_service.MyPermissionsResponse(
|
||||
dataset=enterprise_rbac_service.ResourcePermissionSnapshot(default_permission_keys=["dataset.preview"])
|
||||
)
|
||||
with app.test_request_context("/datasets"):
|
||||
with (
|
||||
patch.object(DatasetService, "get_datasets", return_value=([], 0)) as get_datasets,
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.MyPermissions.get",
|
||||
return_value=permissions,
|
||||
),
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.DatasetAccess.whitelist_resources",
|
||||
return_value=SimpleNamespace(resource_ids=["dataset-whitelist-only"]),
|
||||
),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
method(api, MagicMock(), "tenant-1", current_user)
|
||||
with (
|
||||
app.test_request_context("/datasets"),
|
||||
patch.object(DatasetService, "get_datasets", return_value=([], 0)) as get_datasets,
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.MyPermissions.get",
|
||||
return_value=permissions,
|
||||
),
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.DatasetAccess.whitelist_resources",
|
||||
return_value=SimpleNamespace(resource_ids=["dataset-whitelist-only"]),
|
||||
),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
method(api, MagicMock(), "tenant-1", current_user)
|
||||
assert get_datasets.call_args.kwargs["accessible_dataset_ids"] == ["dataset-whitelist-only"]
|
||||
assert get_datasets.call_args.kwargs["include_own_datasets"] is False
|
||||
|
||||
@@ -384,20 +384,20 @@ class TestDatasetList(_UsesSQLiteSession):
|
||||
]
|
||||
)
|
||||
)
|
||||
with app.test_request_context("/datasets"):
|
||||
with (
|
||||
patch.object(DatasetService, "get_datasets", return_value=([], 0)) as get_datasets,
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.MyPermissions.get",
|
||||
return_value=permissions,
|
||||
),
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.DatasetAccess.whitelist_resources",
|
||||
return_value=SimpleNamespace(resource_ids=["dataset-whitelist-only"]),
|
||||
),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
method(api, self.session, "tenant-1", current_user)
|
||||
with (
|
||||
app.test_request_context("/datasets"),
|
||||
patch.object(DatasetService, "get_datasets", return_value=([], 0)) as get_datasets,
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.MyPermissions.get",
|
||||
return_value=permissions,
|
||||
),
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.DatasetAccess.whitelist_resources",
|
||||
return_value=SimpleNamespace(resource_ids=["dataset-whitelist-only"]),
|
||||
),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
method(api, self.session, "tenant-1", current_user)
|
||||
assert get_datasets.call_args.kwargs["accessible_dataset_ids"] == [
|
||||
"dataset-whitelist-only",
|
||||
]
|
||||
@@ -409,20 +409,20 @@ class TestDatasetList(_UsesSQLiteSession):
|
||||
method = unwrap(api.get)
|
||||
current_user = self._mock_user()
|
||||
permissions = enterprise_rbac_service.MyPermissionsResponse()
|
||||
with app.test_request_context("/datasets?ids=dataset-1"):
|
||||
with (
|
||||
patch.object(DatasetService, "get_datasets_by_ids", return_value=([], 0)) as get_datasets_by_ids,
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.MyPermissions.get",
|
||||
return_value=permissions,
|
||||
),
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.DatasetAccess.whitelist_resources",
|
||||
return_value=SimpleNamespace(resource_ids=[]),
|
||||
),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
method(api, self.session, "tenant-1", current_user)
|
||||
with (
|
||||
app.test_request_context("/datasets?ids=dataset-1"),
|
||||
patch.object(DatasetService, "get_datasets_by_ids", return_value=([], 0)) as get_datasets_by_ids,
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.MyPermissions.get",
|
||||
return_value=permissions,
|
||||
),
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.enterprise_rbac_service.RBACService.DatasetAccess.whitelist_resources",
|
||||
return_value=SimpleNamespace(resource_ids=[]),
|
||||
),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
method(api, self.session, "tenant-1", current_user)
|
||||
session = get_datasets_by_ids.call_args.kwargs["session"]
|
||||
assert session is self.session
|
||||
assert get_datasets_by_ids.call_args.args == (["dataset-1"], "tenant-1")
|
||||
@@ -438,12 +438,12 @@ class TestDatasetList(_UsesSQLiteSession):
|
||||
method = unwrap(api.get)
|
||||
current_user = self._mock_user()
|
||||
datasets = [make_dataset()]
|
||||
with app.test_request_context("/datasets?tag_ids=tag1"):
|
||||
with (
|
||||
patch.object(DatasetService, "get_datasets", return_value=(datasets, 1)),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
resp, status = method(api, self.session, "tenant-1", current_user)
|
||||
with (
|
||||
app.test_request_context("/datasets?tag_ids=tag1"),
|
||||
patch.object(DatasetService, "get_datasets", return_value=(datasets, 1)),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
resp, status = method(api, self.session, "tenant-1", current_user)
|
||||
assert status == 200
|
||||
|
||||
def test_get_allows_legacy_weighted_score_without_weight_type(self, app: Flask):
|
||||
@@ -471,12 +471,12 @@ class TestDatasetList(_UsesSQLiteSession):
|
||||
}
|
||||
)
|
||||
]
|
||||
with app.test_request_context("/datasets"):
|
||||
with (
|
||||
patch.object(DatasetService, "get_datasets", return_value=(datasets, 1)),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
resp, status = method(api, self.session, "tenant-1", current_user)
|
||||
with (
|
||||
app.test_request_context("/datasets"),
|
||||
patch.object(DatasetService, "get_datasets", return_value=(datasets, 1)),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
resp, status = method(api, self.session, "tenant-1", current_user)
|
||||
assert status == 200
|
||||
assert resp["data"][0]["retrieval_model_dict"]["weights"]["weight_type"] is None
|
||||
|
||||
@@ -485,12 +485,12 @@ class TestDatasetList(_UsesSQLiteSession):
|
||||
method = unwrap(api.get)
|
||||
current_user = self._mock_user()
|
||||
datasets = [make_dataset(retrieval_model={"top_k": 4, "score_threshold_enabled": False})]
|
||||
with app.test_request_context("/datasets"):
|
||||
with (
|
||||
patch.object(DatasetService, "get_datasets", return_value=(datasets, 1)),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
resp, status = method(api, self.session, "tenant-1", current_user)
|
||||
with (
|
||||
app.test_request_context("/datasets"),
|
||||
patch.object(DatasetService, "get_datasets", return_value=(datasets, 1)),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
resp, status = method(api, self.session, "tenant-1", current_user)
|
||||
assert status == 200
|
||||
retrieval_model = resp["data"][0]["retrieval_model_dict"]
|
||||
assert retrieval_model["search_method"] == "semantic_search"
|
||||
@@ -509,12 +509,12 @@ class TestDatasetList(_UsesSQLiteSession):
|
||||
]
|
||||
config = MagicMock()
|
||||
config.get_models.return_value = []
|
||||
with app.test_request_context("/datasets"):
|
||||
with (
|
||||
patch.object(DatasetService, "get_datasets", return_value=(datasets, 1)),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=config),
|
||||
):
|
||||
resp, status = method(api, self.session, "tenant-1", current_user)
|
||||
with (
|
||||
app.test_request_context("/datasets"),
|
||||
patch.object(DatasetService, "get_datasets", return_value=(datasets, 1)),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=config),
|
||||
):
|
||||
resp, status = method(api, self.session, "tenant-1", current_user)
|
||||
assert resp["data"][0]["embedding_available"] is False
|
||||
|
||||
def test_partial_members_permission(self, app: Flask):
|
||||
@@ -525,12 +525,12 @@ class TestDatasetList(_UsesSQLiteSession):
|
||||
session = self.session
|
||||
session.add(DatasetPermission(dataset_id="ds-1", account_id="u1", tenant_id="tenant-1"))
|
||||
session.flush()
|
||||
with app.test_request_context("/datasets"):
|
||||
with (
|
||||
patch.object(DatasetService, "get_datasets", return_value=(datasets, 1)),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
resp, status = method(api, session, "tenant-1", current_user)
|
||||
with (
|
||||
app.test_request_context("/datasets"),
|
||||
patch.object(DatasetService, "get_datasets", return_value=(datasets, 1)),
|
||||
patch.object(ProviderManager, "get_configurations", return_value=MagicMock(get_models=lambda **_: [])),
|
||||
):
|
||||
resp, status = method(api, session, "tenant-1", current_user)
|
||||
assert resp["data"][0]["partial_member_list"] == ["u1"]
|
||||
|
||||
|
||||
@@ -554,9 +554,12 @@ class TestDatasetListApiPost(_UsesSQLiteSession):
|
||||
method = unwrap(api.post)
|
||||
payload = {"name": "test"}
|
||||
user = make_account(TenantAccountRole.NORMAL)
|
||||
with app.test_request_context("/datasets", json=payload), patch.object(type(console_ns), "payload", payload):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, DatasetCreatePayload(**payload), self.session, "tenant-1", user)
|
||||
with (
|
||||
app.test_request_context("/datasets", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
method(api, DatasetCreatePayload(**payload), self.session, "tenant-1", user)
|
||||
|
||||
def test_post_duplicate_name(self, app: Flask):
|
||||
api = DatasetListApi()
|
||||
@@ -569,32 +572,41 @@ class TestDatasetListApiPost(_UsesSQLiteSession):
|
||||
patch.object(
|
||||
DatasetService, "create_empty_dataset", side_effect=services.errors.dataset.DatasetNameDuplicateError()
|
||||
),
|
||||
pytest.raises(DatasetNameDuplicateError),
|
||||
):
|
||||
with pytest.raises(DatasetNameDuplicateError):
|
||||
method(api, DatasetCreatePayload(**payload), self.session, "tenant-1", user)
|
||||
method(api, DatasetCreatePayload(**payload), self.session, "tenant-1", user)
|
||||
|
||||
def test_post_invalid_payload_missing_name(self, app: Flask):
|
||||
api = DatasetListApi()
|
||||
method = unwrap(api.post)
|
||||
with app.test_request_context("/datasets", json={}), patch.object(type(console_ns), "payload", {}):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, DatasetCreatePayload(), self.session, "tenant-1", make_account())
|
||||
with (
|
||||
app.test_request_context("/datasets", json={}),
|
||||
patch.object(type(console_ns), "payload", {}),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
method(api, DatasetCreatePayload(), self.session, "tenant-1", make_account())
|
||||
|
||||
def test_post_invalid_indexing_technique(self, app: Flask):
|
||||
api = DatasetListApi()
|
||||
method = unwrap(api.post)
|
||||
payload = {"name": "bad", "indexing_technique": "invalid-tech"}
|
||||
with app.test_request_context("/datasets", json=payload), patch.object(type(console_ns), "payload", payload):
|
||||
with pytest.raises(ValueError, match="Invalid indexing technique"):
|
||||
method(api, DatasetCreatePayload(**payload), self.session, "tenant-1", make_account())
|
||||
with (
|
||||
app.test_request_context("/datasets", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
pytest.raises(ValueError, match="Invalid indexing technique"),
|
||||
):
|
||||
method(api, DatasetCreatePayload(**payload), self.session, "tenant-1", make_account())
|
||||
|
||||
def test_post_invalid_provider(self, app: Flask):
|
||||
api = DatasetListApi()
|
||||
method = unwrap(api.post)
|
||||
payload = {"name": "bad", "provider": "unknown"}
|
||||
with app.test_request_context("/datasets", json=payload), patch.object(type(console_ns), "payload", payload):
|
||||
with pytest.raises(ValueError, match="Invalid provider"):
|
||||
method(api, DatasetCreatePayload(**payload), self.session, "tenant-1", make_account())
|
||||
with (
|
||||
app.test_request_context("/datasets", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
pytest.raises(ValueError, match="Invalid provider"),
|
||||
):
|
||||
method(api, DatasetCreatePayload(**payload), self.session, "tenant-1", make_account())
|
||||
|
||||
|
||||
class TestDatasetApiGet(_UsesSQLiteSession):
|
||||
@@ -671,9 +683,9 @@ class TestDatasetApiGet(_UsesSQLiteSession):
|
||||
with (
|
||||
app.test_request_context(f"/datasets/{dataset_id}"),
|
||||
patch.object(DatasetService, "get_dataset", return_value=None),
|
||||
pytest.raises(NotFound, match="Dataset not found"),
|
||||
):
|
||||
with pytest.raises(NotFound, match="Dataset not found"):
|
||||
method(api, self.session, "tenant", make_account(), dataset_id)
|
||||
method(api, self.session, "tenant", make_account(), dataset_id)
|
||||
|
||||
def test_get_permission_denied(self, app: Flask):
|
||||
api = DatasetApi()
|
||||
@@ -688,9 +700,9 @@ class TestDatasetApiGet(_UsesSQLiteSession):
|
||||
"check_dataset_permission",
|
||||
side_effect=services.errors.account.NoPermissionError("no access"),
|
||||
),
|
||||
pytest.raises(Forbidden, match="no access"),
|
||||
):
|
||||
with pytest.raises(Forbidden, match="no access"):
|
||||
method(api, self.session, "tenant", make_account(), dataset_id)
|
||||
method(api, self.session, "tenant", make_account(), dataset_id)
|
||||
|
||||
def test_get_high_quality_embedding_unavailable(self, app: Flask):
|
||||
api = DatasetApi()
|
||||
@@ -759,9 +771,9 @@ class TestDatasetApiPatch(_UsesSQLiteSession):
|
||||
with (
|
||||
app.test_request_context("/datasets/missing"),
|
||||
patch.object(DatasetService, "get_dataset", return_value=None),
|
||||
pytest.raises(NotFound, match="Dataset not found"),
|
||||
):
|
||||
with pytest.raises(NotFound, match="Dataset not found"):
|
||||
method(api, DatasetUpdatePayload(), self.session, "tenant-1", make_account(), "missing")
|
||||
method(api, DatasetUpdatePayload(), self.session, "tenant-1", make_account(), "missing")
|
||||
|
||||
def test_patch_permission_denied(self, app: Flask):
|
||||
api = DatasetApi()
|
||||
@@ -774,9 +786,9 @@ class TestDatasetApiPatch(_UsesSQLiteSession):
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
patch.object(DatasetService, "get_dataset", return_value=dataset),
|
||||
patch.object(DatasetPermissionService, "check_permission", side_effect=Forbidden("no permission")),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, DatasetUpdatePayload(), self.session, "tenant", make_account(), dataset_id)
|
||||
method(api, DatasetUpdatePayload(), self.session, "tenant", make_account(), dataset_id)
|
||||
|
||||
def test_patch_partial_members_update(self, app: Flask):
|
||||
api = DatasetApi()
|
||||
@@ -835,9 +847,8 @@ class TestDatasetApiDelete(_UsesSQLiteSession):
|
||||
method = unwrap(api.delete)
|
||||
dataset_id = "dataset-id"
|
||||
user = make_account(TenantAccountRole.NORMAL)
|
||||
with app.test_request_context(f"/datasets/{dataset_id}"):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, self.session, user, dataset_id)
|
||||
with app.test_request_context(f"/datasets/{dataset_id}"), pytest.raises(Forbidden):
|
||||
method(api, self.session, user, dataset_id)
|
||||
|
||||
def test_delete_dataset_not_found(self, app: Flask):
|
||||
api = DatasetApi()
|
||||
@@ -847,9 +858,9 @@ class TestDatasetApiDelete(_UsesSQLiteSession):
|
||||
with (
|
||||
app.test_request_context(f"/datasets/{dataset_id}"),
|
||||
patch.object(DatasetService, "delete_dataset", return_value=False),
|
||||
pytest.raises(NotFound, match="Dataset not found"),
|
||||
):
|
||||
with pytest.raises(NotFound, match="Dataset not found"):
|
||||
method(api, self.session, user, dataset_id)
|
||||
method(api, self.session, user, dataset_id)
|
||||
|
||||
def test_delete_dataset_in_use(self, app: Flask):
|
||||
api = DatasetApi()
|
||||
@@ -859,9 +870,9 @@ class TestDatasetApiDelete(_UsesSQLiteSession):
|
||||
with (
|
||||
app.test_request_context(f"/datasets/{dataset_id}"),
|
||||
patch.object(DatasetService, "delete_dataset", side_effect=services.errors.dataset.DatasetInUseError()),
|
||||
pytest.raises(DatasetInUseError),
|
||||
):
|
||||
with pytest.raises(DatasetInUseError):
|
||||
method(api, self.session, user, dataset_id)
|
||||
method(api, self.session, user, dataset_id)
|
||||
|
||||
|
||||
class TestDatasetUseCheckApi(_UsesSQLiteSession):
|
||||
@@ -921,9 +932,9 @@ def test_dataset_scoped_read_permission_denied(app: Flask, api_cls, sqlite_sessi
|
||||
"check_dataset_permission",
|
||||
side_effect=services.errors.account.NoPermissionError("no permission"),
|
||||
),
|
||||
pytest.raises(Forbidden, match="no permission"),
|
||||
):
|
||||
with pytest.raises(Forbidden, match="no permission"):
|
||||
method(api, session, "tenant-1", make_account(), "dataset-1")
|
||||
method(api, session, "tenant-1", make_account(), "dataset-1")
|
||||
|
||||
|
||||
class TestDatasetQueryApi(_UsesSQLiteSession):
|
||||
@@ -1027,9 +1038,9 @@ class TestDatasetQueryApi(_UsesSQLiteSession):
|
||||
with (
|
||||
app.test_request_context("/datasets/queries"),
|
||||
patch.object(DatasetService, "get_dataset", return_value=None),
|
||||
pytest.raises(NotFound, match="Dataset not found"),
|
||||
):
|
||||
with pytest.raises(NotFound, match="Dataset not found"):
|
||||
method(api, self.session, current_user, dataset_id)
|
||||
method(api, self.session, current_user, dataset_id)
|
||||
|
||||
def test_get_queries_permission_denied(self, app: Flask):
|
||||
api = DatasetQueryApi()
|
||||
@@ -1045,9 +1056,9 @@ class TestDatasetQueryApi(_UsesSQLiteSession):
|
||||
"check_dataset_permission",
|
||||
side_effect=services.errors.account.NoPermissionError("no access"),
|
||||
),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, self.session, current_user, dataset_id)
|
||||
method(api, self.session, current_user, dataset_id)
|
||||
|
||||
def test_get_queries_pagination_has_more(self, app: Flask):
|
||||
api = DatasetQueryApi()
|
||||
@@ -1137,14 +1148,14 @@ class TestDatasetIndexingEstimateApi(_UsesSQLiteSession):
|
||||
app.test_request_context("/"),
|
||||
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value=payload),
|
||||
patch("controllers.console.datasets.datasets.DocumentService.estimate_args_validate", return_value=None),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
|
||||
def test_post_llm_bad_request_error(self, app: Flask):
|
||||
api = DatasetIndexingEstimateApi()
|
||||
@@ -1162,14 +1173,14 @@ class TestDatasetIndexingEstimateApi(_UsesSQLiteSession):
|
||||
"controllers.console.datasets.datasets.IndexingRunner.indexing_estimate",
|
||||
side_effect=LLMBadRequestError(),
|
||||
),
|
||||
pytest.raises(ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
|
||||
def test_post_provider_token_not_init(self, app: Flask):
|
||||
api = DatasetIndexingEstimateApi()
|
||||
@@ -1187,14 +1198,14 @@ class TestDatasetIndexingEstimateApi(_UsesSQLiteSession):
|
||||
"controllers.console.datasets.datasets.IndexingRunner.indexing_estimate",
|
||||
side_effect=ProviderTokenNotInitError("token missing"),
|
||||
),
|
||||
pytest.raises(ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
|
||||
def test_post_generic_exception(self, app: Flask):
|
||||
api = DatasetIndexingEstimateApi()
|
||||
@@ -1211,14 +1222,14 @@ class TestDatasetIndexingEstimateApi(_UsesSQLiteSession):
|
||||
patch(
|
||||
"controllers.console.datasets.datasets.IndexingRunner.indexing_estimate", side_effect=Exception("boom")
|
||||
),
|
||||
pytest.raises(IndexingEstimateError),
|
||||
):
|
||||
with pytest.raises(IndexingEstimateError):
|
||||
method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
|
||||
|
||||
class TestDatasetRelatedAppListApi(_UsesSQLiteSession):
|
||||
@@ -1281,9 +1292,9 @@ class TestDatasetRelatedAppListApi(_UsesSQLiteSession):
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch("controllers.console.datasets.datasets.DatasetService.get_dataset", return_value=None),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, self.session, make_account(), "dataset-1")
|
||||
method(api, self.session, make_account(), "dataset-1")
|
||||
|
||||
def test_get_permission_denied(self, app: Flask):
|
||||
api = DatasetRelatedAppListApi()
|
||||
@@ -1296,9 +1307,9 @@ class TestDatasetRelatedAppListApi(_UsesSQLiteSession):
|
||||
"controllers.console.datasets.datasets.DatasetService.check_dataset_permission",
|
||||
side_effect=services.errors.account.NoPermissionError("no permission"),
|
||||
),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, self.session, make_account(), "dataset-1")
|
||||
method(api, self.session, make_account(), "dataset-1")
|
||||
|
||||
def test_get_filters_none_apps(self, app: Flask):
|
||||
api = DatasetRelatedAppListApi()
|
||||
@@ -1457,9 +1468,8 @@ class TestDatasetApiKeyApi(_UsesSQLiteSession):
|
||||
]
|
||||
)
|
||||
session.flush()
|
||||
with app.test_request_context("/"):
|
||||
with pytest.raises(BadRequest) as exc_info:
|
||||
method(api, session, "tenant-1")
|
||||
with app.test_request_context("/"), pytest.raises(BadRequest) as exc_info:
|
||||
method(api, session, "tenant-1")
|
||||
assert exc_info.value.code == 400
|
||||
assert vars(exc_info.value)["data"] == {
|
||||
"message": "Cannot create more than 10 API keys for this resource type.",
|
||||
@@ -1490,9 +1500,8 @@ class TestDatasetApiDeleteApi(_UsesSQLiteSession):
|
||||
api = DatasetApiDeleteApi()
|
||||
method = unwrap(api.delete)
|
||||
session = self.session
|
||||
with app.test_request_context("/"):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, session, "tenant-1", "api-key-id")
|
||||
with app.test_request_context("/"), pytest.raises(NotFound):
|
||||
method(api, session, "tenant-1", "api-key-id")
|
||||
|
||||
|
||||
class TestDatasetEnableApiApi(_UsesSQLiteSession):
|
||||
@@ -1526,16 +1535,16 @@ class TestDatasetEnableApiApi(_UsesSQLiteSession):
|
||||
patch.object(DatasetService, "get_dataset_for_tenant", return_value=dataset),
|
||||
patch.object(DatasetService, "check_dataset_permission"),
|
||||
patch.object(DatasetService, "update_dataset_api_status") as update_status,
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(
|
||||
api,
|
||||
session,
|
||||
"tenant-1",
|
||||
make_account(TenantAccountRole.NORMAL),
|
||||
"dataset-1",
|
||||
"enable",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
session,
|
||||
"tenant-1",
|
||||
make_account(TenantAccountRole.NORMAL),
|
||||
"dataset-1",
|
||||
"enable",
|
||||
)
|
||||
update_status.assert_not_called()
|
||||
|
||||
|
||||
@@ -1649,9 +1658,9 @@ class TestDatasetErrorDocs(_UsesSQLiteSession):
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch.object(DatasetService, "get_dataset_for_tenant", return_value=None) as get_dataset,
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, session, "tenant-1", make_account(), "dataset-1")
|
||||
method(api, session, "tenant-1", make_account(), "dataset-1")
|
||||
get_dataset.assert_called_once_with("dataset-1", "tenant-1", session=session)
|
||||
|
||||
|
||||
@@ -1685,9 +1694,9 @@ class TestDatasetPermissionUserListApi(_UsesSQLiteSession):
|
||||
"controllers.console.datasets.datasets.DatasetService.check_dataset_permission",
|
||||
side_effect=services.errors.account.NoPermissionError("no permission"),
|
||||
),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, self.session, make_account(), "dataset-1")
|
||||
method(api, self.session, make_account(), "dataset-1")
|
||||
|
||||
|
||||
class TestDatasetAutoDisableLogApi(_UsesSQLiteSession):
|
||||
@@ -1718,7 +1727,7 @@ class TestDatasetAutoDisableLogApi(_UsesSQLiteSession):
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch.object(DatasetService, "get_dataset_for_tenant", return_value=None) as get_dataset,
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, session, "tenant-1", make_account(), "dataset-1")
|
||||
method(api, session, "tenant-1", make_account(), "dataset-1")
|
||||
get_dataset.assert_called_once_with("dataset-1", "tenant-1", session=session)
|
||||
|
||||
@@ -339,9 +339,9 @@ class TestGetProcessRuleApi:
|
||||
return_value=document,
|
||||
),
|
||||
patch("controllers.console.datasets.datasets_document.DatasetService.get_dataset", return_value=None),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, session, user)
|
||||
method(api, session, user)
|
||||
|
||||
|
||||
class TestDatasetDocumentListApi:
|
||||
@@ -455,9 +455,9 @@ class TestDatasetDocumentListApi:
|
||||
app.test_request_context("/", json={}),
|
||||
patch.object(type(console_ns), "payload", {}),
|
||||
patch("controllers.console.datasets.datasets_document.DatasetService.get_dataset", return_value=dataset),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, MagicMock(), user, "ds-1")
|
||||
method(api, MagicMock(), user, "ds-1")
|
||||
|
||||
def test_get_with_fetch_true_and_invalid_fetch(self, app: Flask, patch_tenant, patch_dataset, patch_permission):
|
||||
api = DatasetDocumentListApi()
|
||||
@@ -597,9 +597,12 @@ class TestDocumentApi:
|
||||
api = DocumentApi()
|
||||
method = unwrap(api.get)
|
||||
user, tenant_id = patch_tenant
|
||||
with app.test_request_context("/?metadata=wrong"), patch.object(api, "get_document", return_value=MagicMock()):
|
||||
with pytest.raises(InvalidMetadataError):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "doc-1")
|
||||
with (
|
||||
app.test_request_context("/?metadata=wrong"),
|
||||
patch.object(api, "get_document", return_value=MagicMock()),
|
||||
pytest.raises(InvalidMetadataError),
|
||||
):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "doc-1")
|
||||
|
||||
def test_delete_success(self, app: Flask, patch_tenant, patch_dataset):
|
||||
api = DocumentApi()
|
||||
@@ -632,9 +635,9 @@ class TestDocumentApi:
|
||||
"controllers.console.datasets.datasets_document.DocumentService.delete_document",
|
||||
side_effect=services.errors.document.DocumentIndexingError(),
|
||||
),
|
||||
pytest.raises(DocumentIndexingError),
|
||||
):
|
||||
with pytest.raises(DocumentIndexingError):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "doc-1")
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "doc-1")
|
||||
|
||||
|
||||
class TestDocumentDownloadApi:
|
||||
@@ -660,9 +663,12 @@ class TestDocumentProcessingApi:
|
||||
api = DocumentProcessingApi()
|
||||
method = unwrap(api.patch)
|
||||
user = MagicMock(is_dataset_editor=False)
|
||||
with app.test_request_context("/"), patch.object(api, "get_document", return_value=MagicMock()):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1", "pause")
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch.object(api, "get_document", return_value=MagicMock()),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1", "pause")
|
||||
|
||||
def test_resume_from_error_state(self, app: Flask, patch_tenant):
|
||||
api = DocumentProcessingApi()
|
||||
@@ -699,9 +705,12 @@ class TestDocumentProcessingApi:
|
||||
method = unwrap(api.patch)
|
||||
user, tenant_id = patch_tenant
|
||||
document = MagicMock(indexing_status=IndexingStatus.COMPLETED)
|
||||
with app.test_request_context("/"), patch.object(api, "get_document", return_value=document):
|
||||
with pytest.raises(InvalidActionError):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "doc-1", "pause")
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch.object(api, "get_document", return_value=document),
|
||||
pytest.raises(InvalidActionError),
|
||||
):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "doc-1", "pause")
|
||||
|
||||
|
||||
class TestDocumentMetadataApi:
|
||||
@@ -749,9 +758,12 @@ class TestDocumentMetadataApi:
|
||||
method = unwrap(api.put)
|
||||
user, tenant_id = patch_tenant
|
||||
req_data = DocumentMetadataUpdatePayload.model_validate({})
|
||||
with app.test_request_context("/"), patch.object(api, "get_document", return_value=MagicMock()):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, req_data, MagicMock(), tenant_id, user, "ds-1", "doc-1")
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch.object(api, "get_document", return_value=MagicMock()),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
method(api, req_data, MagicMock(), tenant_id, user, "ds-1", "doc-1")
|
||||
|
||||
def test_put_invalid_doc_type(self, app: Flask, patch_tenant):
|
||||
api = DocumentMetadataApi()
|
||||
@@ -766,9 +778,9 @@ class TestDocumentMetadataApi:
|
||||
"controllers.console.datasets.datasets_document.DocumentService.DOCUMENT_METADATA_SCHEMA",
|
||||
{"others": {}},
|
||||
),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, req_data, MagicMock(), tenant_id, user, "ds-1", "doc-1")
|
||||
method(api, req_data, MagicMock(), tenant_id, user, "ds-1", "doc-1")
|
||||
|
||||
|
||||
class TestDocumentStatusApi:
|
||||
@@ -812,9 +824,9 @@ class TestDocumentStatusApi:
|
||||
"controllers.console.datasets.datasets_document.DocumentService.batch_update_document_status",
|
||||
side_effect=ValueError("x"),
|
||||
),
|
||||
pytest.raises(InvalidActionError),
|
||||
):
|
||||
with pytest.raises(InvalidActionError):
|
||||
method(api, MagicMock(), user, "ds-1", "enable")
|
||||
method(api, MagicMock(), user, "ds-1", "enable")
|
||||
|
||||
|
||||
class TestDocumentRetryApi:
|
||||
@@ -923,9 +935,9 @@ class TestDocumentRetryApi:
|
||||
return_value=None,
|
||||
),
|
||||
patch("controllers.console.datasets.datasets_document.DocumentService.retry_document") as retry_document,
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, req_data, session, tenant_id, user, "foreign-dataset")
|
||||
method(api, req_data, session, tenant_id, user, "foreign-dataset")
|
||||
|
||||
session.scalars.assert_not_called()
|
||||
bypass_knowledge_rate_limit.assert_not_called()
|
||||
@@ -1006,9 +1018,9 @@ class TestWebsiteDocumentSyncApi:
|
||||
patch(
|
||||
"controllers.console.datasets.datasets_document.DocumentService.sync_website_document"
|
||||
) as sync_document,
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, session, "tenant-1", user, "ds-1", "doc-1")
|
||||
method(api, session, "tenant-1", user, "ds-1", "doc-1")
|
||||
|
||||
get_document.assert_not_called()
|
||||
sync_document.assert_not_called()
|
||||
@@ -1048,9 +1060,9 @@ class TestDocumentGenerateSummaryApi:
|
||||
"controllers.console.datasets.datasets_document.DocumentService.get_documents_by_ids",
|
||||
return_value=[MagicMock(id="doc-1")],
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, req_data, MagicMock(), user, "ds-1")
|
||||
method(api, req_data, MagicMock(), user, "ds-1")
|
||||
|
||||
def test_generate_not_enabled(self, app: Flask, patch_tenant, patch_permission):
|
||||
api = DocumentGenerateSummaryApi()
|
||||
@@ -1062,9 +1074,9 @@ class TestDocumentGenerateSummaryApi:
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch("controllers.console.datasets.datasets_document.DatasetService.get_dataset", return_value=dataset),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, req_data, MagicMock(), user, "ds-1")
|
||||
method(api, req_data, MagicMock(), user, "ds-1")
|
||||
|
||||
def test_generate_summary_success_with_qa_skip(self, app: Flask, patch_tenant, patch_permission):
|
||||
api = DocumentGenerateSummaryApi()
|
||||
@@ -1136,9 +1148,12 @@ class TestDocumentIndexingEstimateApi:
|
||||
)
|
||||
session = MagicMock()
|
||||
session.scalar.return_value = None
|
||||
with app.test_request_context("/"), patch.object(api, "get_document", return_value=document):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, session, tenant_id, user, "ds-1", "doc-1")
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch.object(api, "get_document", return_value=document),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
method(api, session, tenant_id, user, "ds-1", "doc-1")
|
||||
|
||||
def test_indexing_estimate_generic_exception(self, app: Flask, patch_tenant):
|
||||
api = DocumentIndexingEstimateApi()
|
||||
@@ -1162,18 +1177,21 @@ class TestDocumentIndexingEstimateApi:
|
||||
patch.object(api, "get_document", return_value=document),
|
||||
patch("controllers.console.datasets.datasets_document.ExtractSetting", return_value=MagicMock()),
|
||||
patch("controllers.console.datasets.datasets_document.IndexingRunner", return_value=mock_indexing_runner),
|
||||
pytest.raises(IndexingEstimateError),
|
||||
):
|
||||
with pytest.raises(IndexingEstimateError):
|
||||
method(api, session, tenant_id, user, "ds-1", "doc-1")
|
||||
method(api, session, tenant_id, user, "ds-1", "doc-1")
|
||||
|
||||
def test_get_finished(self, app: Flask, patch_tenant):
|
||||
api = DocumentIndexingEstimateApi()
|
||||
method = unwrap(api.get)
|
||||
user, tenant_id = patch_tenant
|
||||
document = MagicMock(indexing_status=IndexingStatus.COMPLETED)
|
||||
with app.test_request_context("/"), patch.object(api, "get_document", return_value=document):
|
||||
with pytest.raises(DocumentAlreadyFinishedError):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "doc-1")
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch.object(api, "get_document", return_value=document),
|
||||
pytest.raises(DocumentAlreadyFinishedError),
|
||||
):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "doc-1")
|
||||
|
||||
|
||||
class TestDocumentBatchDownloadZipApi:
|
||||
@@ -1182,9 +1200,12 @@ class TestDocumentBatchDownloadZipApi:
|
||||
method = unwrap(api.post)
|
||||
user, tenant_id = patch_tenant
|
||||
payload: dict[str, list[str]] = {"document_ids": []}
|
||||
with app.test_request_context("/", json=payload), patch.object(type(console_ns), "payload", payload):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1")
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1")
|
||||
|
||||
|
||||
class TestDatasetDocumentListApiDelete:
|
||||
@@ -1212,9 +1233,9 @@ class TestDatasetDocumentListApiDelete:
|
||||
"controllers.console.datasets.datasets_document.DocumentService.delete_documents",
|
||||
side_effect=services.errors.document.DocumentIndexingError(),
|
||||
),
|
||||
pytest.raises(DocumentIndexingError),
|
||||
):
|
||||
with pytest.raises(DocumentIndexingError):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1")
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1")
|
||||
|
||||
def test_delete_dataset_not_found(self, app: Flask, patch_tenant, bypass_knowledge_rate_limit):
|
||||
"""Test deletion when dataset not found"""
|
||||
@@ -1230,9 +1251,9 @@ class TestDatasetDocumentListApiDelete:
|
||||
patch(
|
||||
"controllers.console.datasets.datasets_document.DocumentService.delete_documents"
|
||||
) as delete_documents,
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, MagicMock(), tenant_id, user, "foreign-dataset")
|
||||
method(api, MagicMock(), tenant_id, user, "foreign-dataset")
|
||||
|
||||
bypass_knowledge_rate_limit.assert_not_called()
|
||||
delete_documents.assert_not_called()
|
||||
@@ -1302,18 +1323,24 @@ class TestDocumentBatchIndexingEstimateApi:
|
||||
data_source_info_dict={},
|
||||
doc_form=IndexStructureType.PARAGRAPH_INDEX,
|
||||
)
|
||||
with app.test_request_context("/"), patch.object(api, "get_batch_documents", return_value=[document]):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "batch-1")
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch.object(api, "get_batch_documents", return_value=[document]),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "batch-1")
|
||||
|
||||
def test_get_batch_estimate_invalid_batch(self, app: Flask, patch_tenant):
|
||||
"""Test batch estimation with invalid batch"""
|
||||
api = DocumentBatchIndexingEstimateApi()
|
||||
method = unwrap(api.get)
|
||||
user, tenant_id = patch_tenant
|
||||
with app.test_request_context("/"), patch.object(api, "get_batch_documents", side_effect=NotFound()):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "invalid-batch")
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch.object(api, "get_batch_documents", side_effect=NotFound()),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "invalid-batch")
|
||||
|
||||
|
||||
class TestDocumentBatchIndexingStatusApi:
|
||||
@@ -1366,9 +1393,12 @@ class TestDocumentBatchIndexingStatusApi:
|
||||
api = DocumentBatchIndexingStatusApi()
|
||||
method = unwrap(api.get)
|
||||
user, _ = patch_tenant
|
||||
with app.test_request_context("/"), patch.object(api, "get_batch_documents", side_effect=NotFound()):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, MagicMock(), user, "ds-1", "invalid-batch")
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch.object(api, "get_batch_documents", side_effect=NotFound()),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
method(api, MagicMock(), user, "ds-1", "invalid-batch")
|
||||
|
||||
|
||||
class TestDocumentIndexingStatusApi:
|
||||
@@ -1403,9 +1433,12 @@ class TestDocumentIndexingStatusApi:
|
||||
api = DocumentIndexingStatusApi()
|
||||
method = unwrap(api.get)
|
||||
user, tenant_id = patch_tenant
|
||||
with app.test_request_context("/"), patch.object(api, "get_document", side_effect=NotFound()):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "invalid-doc")
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch.object(api, "get_document", side_effect=NotFound()),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "invalid-doc")
|
||||
|
||||
|
||||
class TestDocumentRenameApi:
|
||||
@@ -1482,9 +1515,9 @@ class TestDocumentGenerateSummaryApiSuccess:
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch("controllers.console.datasets.datasets_document.DatasetService.get_dataset", return_value=dataset),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, req_data, MagicMock(), user, "ds-1")
|
||||
method(api, req_data, MagicMock(), user, "ds-1")
|
||||
|
||||
|
||||
class TestDocumentProcessingApiResume:
|
||||
@@ -1494,9 +1527,12 @@ class TestDocumentProcessingApiResume:
|
||||
method = unwrap(api.patch)
|
||||
user, tenant_id = patch_tenant
|
||||
document = MagicMock(indexing_status=IndexingStatus.COMPLETED, is_paused=False)
|
||||
with app.test_request_context("/"), patch.object(api, "get_document", return_value=document):
|
||||
with pytest.raises(InvalidActionError):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "doc-1", "resume")
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch.object(api, "get_document", return_value=document),
|
||||
pytest.raises(InvalidActionError),
|
||||
):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "doc-1", "resume")
|
||||
|
||||
|
||||
class TestDocumentPermissionCases:
|
||||
@@ -1513,9 +1549,9 @@ class TestDocumentPermissionCases:
|
||||
"controllers.console.datasets.datasets_document.DatasetService.check_dataset_permission",
|
||||
side_effect=services.errors.account.NoPermissionError("No permission"),
|
||||
),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "batch-1")
|
||||
method(api, MagicMock(), tenant_id, user, "ds-1", "batch-1")
|
||||
|
||||
def test_document_batch_get_documents_not_found(self, app: Flask, patch_tenant):
|
||||
api = DocumentBatchIndexingEstimateApi()
|
||||
@@ -1583,9 +1619,9 @@ class TestDocumentPermissionCases:
|
||||
"controllers.console.datasets.datasets_document.DatasetService.check_dataset_permission",
|
||||
side_effect=services.errors.account.NoPermissionError("No permission"),
|
||||
),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, session, user)
|
||||
method(api, session, user)
|
||||
|
||||
|
||||
class TestDocumentListAdvancedCases:
|
||||
|
||||
@@ -256,9 +256,9 @@ class TestDatasetDocumentSegmentListApi(SQLiteControllerTest):
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch("controllers.console.datasets.datasets_segments.DatasetService.get_dataset", return_value=None),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1")
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1")
|
||||
|
||||
def test_get_permission_denied(self, app: Flask):
|
||||
api = DatasetDocumentSegmentListApi()
|
||||
@@ -272,9 +272,9 @@ class TestDatasetDocumentSegmentListApi(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.DatasetService.check_dataset_permission",
|
||||
side_effect=services.errors.account.NoPermissionError("no access"),
|
||||
),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1")
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1")
|
||||
|
||||
|
||||
class TestDatasetDocumentSegmentApi(SQLiteControllerTest):
|
||||
@@ -321,9 +321,9 @@ class TestDatasetDocumentSegmentApi(SQLiteControllerTest):
|
||||
return_value=None,
|
||||
),
|
||||
patch("controllers.console.datasets.datasets_segments.redis_client.get", return_value=b"running"),
|
||||
pytest.raises(InvalidActionError),
|
||||
):
|
||||
with pytest.raises(InvalidActionError):
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1", "disable")
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1", "disable")
|
||||
|
||||
def test_patch_llm_bad_request(self, app: Flask):
|
||||
api = DatasetDocumentSegmentApi()
|
||||
@@ -349,9 +349,9 @@ class TestDatasetDocumentSegmentApi(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.ModelManager.get_model_instance",
|
||||
side_effect=LLMBadRequestError(),
|
||||
),
|
||||
pytest.raises(ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1", "enable")
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1", "enable")
|
||||
|
||||
def test_patch_provider_token_not_init(self, app: Flask):
|
||||
api = DatasetDocumentSegmentApi()
|
||||
@@ -377,9 +377,9 @@ class TestDatasetDocumentSegmentApi(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.ModelManager.get_model_instance",
|
||||
side_effect=ProviderTokenNotInitError("token missing"),
|
||||
),
|
||||
pytest.raises(ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1", "enable")
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1", "enable")
|
||||
|
||||
|
||||
class TestDatasetDocumentSegmentAddApi(SQLiteControllerTest):
|
||||
@@ -434,11 +434,9 @@ class TestDatasetDocumentSegmentAddApi(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.ModelManager.get_model_instance",
|
||||
side_effect=LLMBadRequestError(),
|
||||
),
|
||||
pytest.raises(ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(
|
||||
api, SegmentCreatePayload(content="test content"), self.session, "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
method(api, SegmentCreatePayload(content="test content"), self.session, "tenant-1", user, "ds-1", "doc-1")
|
||||
|
||||
def test_post_provider_token_not_init(self, app: Flask):
|
||||
api = DatasetDocumentSegmentAddApi()
|
||||
@@ -458,11 +456,9 @@ class TestDatasetDocumentSegmentAddApi(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.ModelManager.get_model_instance",
|
||||
side_effect=ProviderTokenNotInitError("token missing"),
|
||||
),
|
||||
pytest.raises(ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(
|
||||
api, SegmentCreatePayload(content="test content"), self.session, "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
method(api, SegmentCreatePayload(content="test content"), self.session, "tenant-1", user, "ds-1", "doc-1")
|
||||
|
||||
|
||||
class TestDatasetDocumentSegmentUpdateApi(SQLiteControllerTest):
|
||||
@@ -529,18 +525,18 @@ class TestDatasetDocumentSegmentUpdateApi(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.DatasetService.check_dataset_permission",
|
||||
return_value=None,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
SegmentUpdatePayload(content="test content"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
SegmentUpdatePayload(content="test content"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
|
||||
def test_patch_segment_not_found(self, app: Flask):
|
||||
api = DatasetDocumentSegmentUpdateApi()
|
||||
@@ -565,18 +561,18 @@ class TestDatasetDocumentSegmentUpdateApi(SQLiteControllerTest):
|
||||
patch(
|
||||
"controllers.console.datasets.datasets_segments.SegmentService.get_segment_by_ref", return_value=None
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
SegmentUpdatePayload(content="test content"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
SegmentUpdatePayload(content="test content"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
|
||||
def test_patch_llm_bad_request(self, app: Flask):
|
||||
api = DatasetDocumentSegmentUpdateApi()
|
||||
@@ -604,18 +600,18 @@ class TestDatasetDocumentSegmentUpdateApi(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.ModelManager.get_model_instance",
|
||||
side_effect=LLMBadRequestError(),
|
||||
),
|
||||
pytest.raises(ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(
|
||||
api,
|
||||
SegmentUpdatePayload(content="test content"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
SegmentUpdatePayload(content="test content"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
|
||||
|
||||
class TestDatasetDocumentSegmentBatchImportApi(SQLiteControllerTest):
|
||||
@@ -675,17 +671,17 @@ class TestDatasetDocumentSegmentBatchImportApi(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.DatasetService.get_dataset_for_tenant",
|
||||
return_value=None,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
|
||||
def test_post_document_not_found(self, app: Flask):
|
||||
api = DatasetDocumentSegmentBatchImportApi()
|
||||
@@ -703,17 +699,17 @@ class TestDatasetDocumentSegmentBatchImportApi(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.DatasetRefService.get_document_by_ref",
|
||||
return_value=None,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
|
||||
def test_post_upload_file_not_found(self, app: Flask):
|
||||
api = DatasetDocumentSegmentBatchImportApi()
|
||||
@@ -731,17 +727,17 @@ class TestDatasetDocumentSegmentBatchImportApi(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.DatasetRefService.get_document_by_ref",
|
||||
return_value=_document(),
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
|
||||
def test_post_invalid_file_type(self, app: Flask):
|
||||
api = DatasetDocumentSegmentBatchImportApi()
|
||||
@@ -762,17 +758,17 @@ class TestDatasetDocumentSegmentBatchImportApi(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.DatasetRefService.get_document_by_ref",
|
||||
return_value=_document(),
|
||||
),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
|
||||
def test_post_async_task_failure(self, app: Flask):
|
||||
api = DatasetDocumentSegmentBatchImportApi()
|
||||
@@ -815,9 +811,9 @@ class TestDatasetDocumentSegmentBatchImportApi(SQLiteControllerTest):
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch("controllers.console.datasets.datasets_segments.redis_client.get", return_value=None),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, job_id="job-1")
|
||||
method(api, job_id="job-1")
|
||||
|
||||
|
||||
class TestChildChunkAddApi(SQLiteControllerTest):
|
||||
@@ -921,18 +917,18 @@ class TestChildChunkAddApi(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.SegmentService.create_child_chunk",
|
||||
side_effect=ChildChunkIndexingServiceError("fail"),
|
||||
),
|
||||
pytest.raises(ChildChunkIndexingError),
|
||||
):
|
||||
with pytest.raises(ChildChunkIndexingError):
|
||||
method(
|
||||
api,
|
||||
ChildChunkCreatePayload(content="child"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
ChildChunkCreatePayload(content="child"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
|
||||
def test_post_permission_denied(self, app: Flask):
|
||||
api = ChildChunkAddApi()
|
||||
@@ -950,18 +946,18 @@ class TestChildChunkAddApi(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.DatasetService.check_dataset_permission",
|
||||
side_effect=services.errors.account.NoPermissionError("no access"),
|
||||
),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(
|
||||
api,
|
||||
ChildChunkCreatePayload(content="child"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
ChildChunkCreatePayload(content="child"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
|
||||
|
||||
class TestChildChunkUpdateApi(SQLiteControllerTest):
|
||||
@@ -1023,9 +1019,9 @@ class TestChildChunkUpdateApi(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.SegmentService.delete_child_chunk",
|
||||
side_effect=ChildChunkDeleteIndexServiceError("fail"),
|
||||
),
|
||||
pytest.raises(ChildChunkDeleteIndexError),
|
||||
):
|
||||
with pytest.raises(ChildChunkDeleteIndexError):
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1", "seg-1", "cc-1")
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1", "seg-1", "cc-1")
|
||||
|
||||
def test_delete_child_chunk_not_found(self, app: Flask):
|
||||
api = ChildChunkUpdateApi()
|
||||
@@ -1049,9 +1045,9 @@ class TestChildChunkUpdateApi(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.DatasetService.check_dataset_permission",
|
||||
return_value=None,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1", "seg-1", "cc-1")
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1", "seg-1", "cc-1")
|
||||
|
||||
def test_patch_child_chunk_not_found(self, app: Flask):
|
||||
api = ChildChunkUpdateApi()
|
||||
@@ -1077,19 +1073,19 @@ class TestChildChunkUpdateApi(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.DatasetService.check_dataset_permission",
|
||||
return_value=None,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
ChildChunkUpdatePayload(content="updated child"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
"cc-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
ChildChunkUpdatePayload(content="updated child"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
"cc-1",
|
||||
)
|
||||
|
||||
|
||||
class TestSegmentListAdvancedCases(SQLiteControllerTest):
|
||||
@@ -1171,9 +1167,9 @@ class TestSegmentListAdvancedCases(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.DatasetService.check_dataset_permission",
|
||||
side_effect=services.errors.account.NoPermissionError("No permission"),
|
||||
),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1")
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1")
|
||||
|
||||
def test_segment_list_dataset_not_found(self, app: Flask):
|
||||
"""Test segment list with dataset not found"""
|
||||
@@ -1183,9 +1179,9 @@ class TestSegmentListAdvancedCases(SQLiteControllerTest):
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch("controllers.console.datasets.datasets_segments.DatasetService.get_dataset", return_value=None),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1")
|
||||
method(api, self.session, "tenant-1", user, "ds-1", "doc-1")
|
||||
|
||||
|
||||
class TestSegmentOperationCases(SQLiteControllerTest):
|
||||
@@ -1210,17 +1206,17 @@ class TestSegmentOperationCases(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.SegmentService.create_segment",
|
||||
side_effect=ProviderTokenNotInitError("Token not init"),
|
||||
),
|
||||
pytest.raises(ProviderTokenNotInitError),
|
||||
):
|
||||
with pytest.raises(ProviderTokenNotInitError):
|
||||
method(
|
||||
api,
|
||||
SegmentCreatePayload(content="test content"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
SegmentCreatePayload(content="test content"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
|
||||
def test_batch_import_with_document_not_found(self, app: Flask):
|
||||
"""Test batch import with document not found"""
|
||||
@@ -1240,17 +1236,17 @@ class TestSegmentOperationCases(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.DatasetRefService.get_document_by_ref",
|
||||
return_value=None,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
|
||||
def test_batch_import_with_invalid_file(self, app: Flask):
|
||||
"""Test batch import with invalid file type"""
|
||||
@@ -1271,17 +1267,17 @@ class TestSegmentOperationCases(SQLiteControllerTest):
|
||||
"controllers.console.datasets.datasets_segments.DatasetRefService.get_document_by_ref",
|
||||
return_value=document,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
self.session,
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
|
||||
def test_batch_import_with_async_task_failure(self, app: Flask):
|
||||
api = DatasetDocumentSegmentBatchImportApi()
|
||||
@@ -1327,6 +1323,6 @@ class TestSegmentOperationCases(SQLiteControllerTest):
|
||||
with (
|
||||
app.test_request_context("/?job_id=invalid-job"),
|
||||
patch("controllers.console.datasets.datasets_segments.redis_client.get", return_value=None),
|
||||
pytest.raises(ValueError),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, "invalid-job")
|
||||
method(api, "invalid-job")
|
||||
|
||||
@@ -251,9 +251,9 @@ class TestExternalApiTemplateListApi(_UsesSQLiteSession):
|
||||
app.test_request_context("/"),
|
||||
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value=payload),
|
||||
patch.object(ExternalDatasetService, "validate_api_list"),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, ExternalKnowledgeApiPayload.model_validate(payload), self.session, "tenant-1", current_user)
|
||||
method(api, ExternalKnowledgeApiPayload.model_validate(payload), self.session, "tenant-1", current_user)
|
||||
|
||||
def test_post_duplicate_name(self, app: Flask, current_user: Account):
|
||||
api = ExternalApiTemplateListApi()
|
||||
@@ -270,9 +270,9 @@ class TestExternalApiTemplateListApi(_UsesSQLiteSession):
|
||||
"create_external_knowledge_api",
|
||||
side_effect=services.errors.dataset.DatasetNameDuplicateError(),
|
||||
),
|
||||
pytest.raises(DatasetNameDuplicateError),
|
||||
):
|
||||
with pytest.raises(DatasetNameDuplicateError):
|
||||
method(api, ExternalKnowledgeApiPayload.model_validate(payload), self.session, "tenant-1", current_user)
|
||||
method(api, ExternalKnowledgeApiPayload.model_validate(payload), self.session, "tenant-1", current_user)
|
||||
|
||||
|
||||
class TestExternalApiTemplateApi(_UsesSQLiteSession):
|
||||
@@ -310,9 +310,9 @@ class TestExternalApiTemplateApi(_UsesSQLiteSession):
|
||||
"get_external_knowledge_api",
|
||||
return_value=None,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, self.session, "tenant-1", "api-id")
|
||||
method(api, self.session, "tenant-1", "api-id")
|
||||
|
||||
def test_patch_success_uses_validated_payload_and_returns_template(self, app: Flask, current_user: Account):
|
||||
api = ExternalApiTemplateApi()
|
||||
@@ -366,9 +366,8 @@ class TestExternalApiTemplateApi(_UsesSQLiteSession):
|
||||
api = ExternalApiTemplateApi()
|
||||
method = inspect.unwrap(api.delete)
|
||||
|
||||
with app.test_request_context("/"):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, self.session, "tenant-1", current_user, "api-id")
|
||||
with app.test_request_context("/"), pytest.raises(Forbidden):
|
||||
method(api, self.session, "tenant-1", current_user, "api-id")
|
||||
|
||||
|
||||
class TestExternalApiUseCheckApi(_UsesSQLiteSession):
|
||||
@@ -454,11 +453,9 @@ class TestExternalDatasetCreateApi(_UsesSQLiteSession):
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value=payload),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(
|
||||
api, ExternalDatasetCreatePayload.model_validate(payload), self.session, "tenant-1", current_user
|
||||
)
|
||||
method(api, ExternalDatasetCreatePayload.model_validate(payload), self.session, "tenant-1", current_user)
|
||||
|
||||
|
||||
class TestExternalKnowledgeHitTestingApi(_UsesSQLiteSession):
|
||||
@@ -473,9 +470,9 @@ class TestExternalKnowledgeHitTestingApi(_UsesSQLiteSession):
|
||||
"get_dataset",
|
||||
return_value=None,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, ExternalHitTestingPayload(query="test"), self.session, current_user, "dataset-id")
|
||||
method(api, ExternalHitTestingPayload(query="test"), self.session, current_user, "dataset-id")
|
||||
|
||||
def test_hit_testing_success(self, app: Flask, current_user: Account):
|
||||
api = ExternalKnowledgeHitTestingApi()
|
||||
@@ -551,9 +548,9 @@ class TestExternalApiTemplateListApiAdvanced(_UsesSQLiteSession):
|
||||
"controllers.console.datasets.external.ExternalDatasetService.create_external_knowledge_api",
|
||||
side_effect=services.errors.dataset.DatasetNameDuplicateError("Duplicate"),
|
||||
),
|
||||
pytest.raises(DatasetNameDuplicateError),
|
||||
):
|
||||
with pytest.raises(DatasetNameDuplicateError):
|
||||
method(api, ExternalKnowledgeApiPayload.model_validate(payload), self.session, "tenant-1", current_user)
|
||||
method(api, ExternalKnowledgeApiPayload.model_validate(payload), self.session, "tenant-1", current_user)
|
||||
|
||||
def test_get_with_pagination(self, app: Flask):
|
||||
api = ExternalApiTemplateListApi()
|
||||
@@ -596,11 +593,12 @@ class TestExternalDatasetCreateApiAdvanced(_UsesSQLiteSession):
|
||||
"description": "A dataset",
|
||||
}
|
||||
|
||||
with app.test_request_context("/", json=payload), patch.object(type(console_ns), "payload", payload):
|
||||
with pytest.raises(Forbidden):
|
||||
method(
|
||||
api, ExternalDatasetCreatePayload.model_validate(payload), self.session, "tenant-1", current_user
|
||||
)
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
method(api, ExternalDatasetCreatePayload.model_validate(payload), self.session, "tenant-1", current_user)
|
||||
|
||||
|
||||
class TestExternalKnowledgeHitTestingApiAdvanced(_UsesSQLiteSession):
|
||||
@@ -621,9 +619,9 @@ class TestExternalKnowledgeHitTestingApiAdvanced(_UsesSQLiteSession):
|
||||
"controllers.console.datasets.external.DatasetService.get_dataset",
|
||||
return_value=None,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, ExternalHitTestingPayload.model_validate(payload), self.session, current_user, "ds-1")
|
||||
method(api, ExternalHitTestingPayload.model_validate(payload), self.session, current_user, "ds-1")
|
||||
|
||||
def test_hit_testing_with_custom_retrieval_model(self, app: Flask, current_user: Account):
|
||||
api = ExternalKnowledgeHitTestingApi()
|
||||
|
||||
@@ -202,9 +202,9 @@ class TestHitTestingApi:
|
||||
"get_and_validate_dataset",
|
||||
side_effect=NotFound("Dataset not found"),
|
||||
),
|
||||
pytest.raises(NotFound, match="Dataset not found"),
|
||||
):
|
||||
with pytest.raises(NotFound, match="Dataset not found"):
|
||||
method(api, MagicMock(), account, "tenant-1", dataset_id)
|
||||
method(api, MagicMock(), account, "tenant-1", dataset_id)
|
||||
|
||||
def test_hit_testing_invalid_args(self, app: Flask, dataset, dataset_id, account: Account):
|
||||
api = HitTestingApi()
|
||||
@@ -232,6 +232,6 @@ class TestHitTestingApi:
|
||||
"hit_testing_args_check",
|
||||
side_effect=ValueError("Invalid parameters"),
|
||||
),
|
||||
pytest.raises(ValueError, match="Invalid parameters"),
|
||||
):
|
||||
with pytest.raises(ValueError, match="Invalid parameters"):
|
||||
method(api, MagicMock(), account, "tenant-1", dataset_id)
|
||||
method(api, MagicMock(), account, "tenant-1", dataset_id)
|
||||
|
||||
@@ -99,13 +99,15 @@ class TestGetAndValidateDataset:
|
||||
assert result == dataset
|
||||
|
||||
def test_dataset_not_found(self, account):
|
||||
with patch.object(
|
||||
DatasetService,
|
||||
"get_dataset",
|
||||
return_value=None,
|
||||
with (
|
||||
patch.object(
|
||||
DatasetService,
|
||||
"get_dataset",
|
||||
return_value=None,
|
||||
),
|
||||
pytest.raises(NotFound, match="Dataset not found"),
|
||||
):
|
||||
with pytest.raises(NotFound, match="Dataset not found"):
|
||||
DatasetsHitTestingBase.get_and_validate_dataset(Mock(), "dataset-1", account, "tenant-1")
|
||||
DatasetsHitTestingBase.get_and_validate_dataset(Mock(), "dataset-1", account, "tenant-1")
|
||||
|
||||
def test_permission_denied(self, dataset, account):
|
||||
with (
|
||||
@@ -119,9 +121,9 @@ class TestGetAndValidateDataset:
|
||||
"check_dataset_permission",
|
||||
side_effect=services.errors.account.NoPermissionError("no access"),
|
||||
),
|
||||
pytest.raises(Forbidden, match="no access"),
|
||||
):
|
||||
with pytest.raises(Forbidden, match="no access"):
|
||||
DatasetsHitTestingBase.get_and_validate_dataset(Mock(), "dataset-1", account, "tenant-1")
|
||||
DatasetsHitTestingBase.get_and_validate_dataset(Mock(), "dataset-1", account, "tenant-1")
|
||||
|
||||
|
||||
class TestHitTestingArgsCheck:
|
||||
@@ -223,73 +225,89 @@ class TestPerformHitTesting:
|
||||
DatasetsHitTestingBase._prepare_hit_testing_records(["record"])
|
||||
|
||||
def test_index_not_initialized(self, dataset, account):
|
||||
with patch.object(
|
||||
HitTestingService,
|
||||
"retrieve",
|
||||
side_effect=services.errors.index.IndexNotInitializedError(),
|
||||
with (
|
||||
patch.object(
|
||||
HitTestingService,
|
||||
"retrieve",
|
||||
side_effect=services.errors.index.IndexNotInitializedError(),
|
||||
),
|
||||
pytest.raises(DatasetNotInitializedError),
|
||||
):
|
||||
with pytest.raises(DatasetNotInitializedError):
|
||||
DatasetsHitTestingBase.perform_hit_testing(Mock(), dataset, {"query": "hello"}, account, "tenant-1")
|
||||
DatasetsHitTestingBase.perform_hit_testing(Mock(), dataset, {"query": "hello"}, account, "tenant-1")
|
||||
|
||||
def test_provider_token_not_init(self, dataset, account):
|
||||
with patch.object(
|
||||
HitTestingService,
|
||||
"retrieve",
|
||||
side_effect=ProviderTokenNotInitError("token missing"),
|
||||
with (
|
||||
patch.object(
|
||||
HitTestingService,
|
||||
"retrieve",
|
||||
side_effect=ProviderTokenNotInitError("token missing"),
|
||||
),
|
||||
pytest.raises(ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
DatasetsHitTestingBase.perform_hit_testing(Mock(), dataset, {"query": "hello"}, account, "tenant-1")
|
||||
DatasetsHitTestingBase.perform_hit_testing(Mock(), dataset, {"query": "hello"}, account, "tenant-1")
|
||||
|
||||
def test_quota_exceeded(self, dataset, account):
|
||||
with patch.object(
|
||||
HitTestingService,
|
||||
"retrieve",
|
||||
side_effect=QuotaExceededError(),
|
||||
with (
|
||||
patch.object(
|
||||
HitTestingService,
|
||||
"retrieve",
|
||||
side_effect=QuotaExceededError(),
|
||||
),
|
||||
pytest.raises(ProviderQuotaExceededError),
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
DatasetsHitTestingBase.perform_hit_testing(Mock(), dataset, {"query": "hello"}, account, "tenant-1")
|
||||
DatasetsHitTestingBase.perform_hit_testing(Mock(), dataset, {"query": "hello"}, account, "tenant-1")
|
||||
|
||||
def test_model_not_supported(self, dataset, account):
|
||||
with patch.object(
|
||||
HitTestingService,
|
||||
"retrieve",
|
||||
side_effect=ModelCurrentlyNotSupportError(),
|
||||
with (
|
||||
patch.object(
|
||||
HitTestingService,
|
||||
"retrieve",
|
||||
side_effect=ModelCurrentlyNotSupportError(),
|
||||
),
|
||||
pytest.raises(ProviderModelCurrentlyNotSupportError),
|
||||
):
|
||||
with pytest.raises(ProviderModelCurrentlyNotSupportError):
|
||||
DatasetsHitTestingBase.perform_hit_testing(Mock(), dataset, {"query": "hello"}, account, "tenant-1")
|
||||
DatasetsHitTestingBase.perform_hit_testing(Mock(), dataset, {"query": "hello"}, account, "tenant-1")
|
||||
|
||||
def test_llm_bad_request(self, dataset, account):
|
||||
with patch.object(
|
||||
HitTestingService,
|
||||
"retrieve",
|
||||
side_effect=LLMBadRequestError("bad request"),
|
||||
with (
|
||||
patch.object(
|
||||
HitTestingService,
|
||||
"retrieve",
|
||||
side_effect=LLMBadRequestError("bad request"),
|
||||
),
|
||||
pytest.raises(ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
DatasetsHitTestingBase.perform_hit_testing(Mock(), dataset, {"query": "hello"}, account, "tenant-1")
|
||||
DatasetsHitTestingBase.perform_hit_testing(Mock(), dataset, {"query": "hello"}, account, "tenant-1")
|
||||
|
||||
def test_invoke_error(self, dataset, account):
|
||||
with patch.object(
|
||||
HitTestingService,
|
||||
"retrieve",
|
||||
side_effect=InvokeError("invoke failed"),
|
||||
with (
|
||||
patch.object(
|
||||
HitTestingService,
|
||||
"retrieve",
|
||||
side_effect=InvokeError("invoke failed"),
|
||||
),
|
||||
pytest.raises(CompletionRequestError),
|
||||
):
|
||||
with pytest.raises(CompletionRequestError):
|
||||
DatasetsHitTestingBase.perform_hit_testing(Mock(), dataset, {"query": "hello"}, account, "tenant-1")
|
||||
DatasetsHitTestingBase.perform_hit_testing(Mock(), dataset, {"query": "hello"}, account, "tenant-1")
|
||||
|
||||
def test_value_error(self, dataset, account):
|
||||
with patch.object(
|
||||
HitTestingService,
|
||||
"retrieve",
|
||||
side_effect=ValueError("bad args"),
|
||||
with (
|
||||
patch.object(
|
||||
HitTestingService,
|
||||
"retrieve",
|
||||
side_effect=ValueError("bad args"),
|
||||
),
|
||||
pytest.raises(ValueError, match="bad args"),
|
||||
):
|
||||
with pytest.raises(ValueError, match="bad args"):
|
||||
DatasetsHitTestingBase.perform_hit_testing(Mock(), dataset, {"query": "hello"}, account, "tenant-1")
|
||||
DatasetsHitTestingBase.perform_hit_testing(Mock(), dataset, {"query": "hello"}, account, "tenant-1")
|
||||
|
||||
def test_unexpected_error(self, dataset, account):
|
||||
with patch.object(
|
||||
HitTestingService,
|
||||
"retrieve",
|
||||
side_effect=Exception("boom"),
|
||||
with (
|
||||
patch.object(
|
||||
HitTestingService,
|
||||
"retrieve",
|
||||
side_effect=Exception("boom"),
|
||||
),
|
||||
pytest.raises(InternalServerError, match="boom"),
|
||||
):
|
||||
with pytest.raises(InternalServerError, match="boom"):
|
||||
DatasetsHitTestingBase.perform_hit_testing(Mock(), dataset, {"query": "hello"}, account, "tenant-1")
|
||||
DatasetsHitTestingBase.perform_hit_testing(Mock(), dataset, {"query": "hello"}, account, "tenant-1")
|
||||
|
||||
@@ -97,11 +97,9 @@ class TestDatasetMetadataCreateApi:
|
||||
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value=valid_payload),
|
||||
patch.object(MetadataArgs, "model_validate", return_value=MagicMock()),
|
||||
patch.object(DatasetService, "get_dataset", return_value=None),
|
||||
pytest.raises(NotFound, match="Dataset not found"),
|
||||
):
|
||||
with pytest.raises(NotFound, match="Dataset not found"):
|
||||
method(
|
||||
api, MetadataArgs(type="string", name="author"), MagicMock(), "tenant-1", current_user, dataset_id
|
||||
)
|
||||
method(api, MetadataArgs(type="string", name="author"), MagicMock(), "tenant-1", current_user, dataset_id)
|
||||
|
||||
|
||||
class TestDatasetMetadataGetApi:
|
||||
@@ -138,9 +136,9 @@ class TestDatasetMetadataGetApi:
|
||||
patch.object(DatasetService, "get_dataset_for_tenant", return_value=None) as get_dataset,
|
||||
patch.object(DatasetService, "check_dataset_permission") as check_permission,
|
||||
patch.object(MetadataService, "get_dataset_metadatas") as get_metadata,
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, session, "tenant-1", current_user, dataset_id)
|
||||
method(api, session, "tenant-1", current_user, dataset_id)
|
||||
|
||||
get_dataset.assert_called_once_with(str(dataset_id), "tenant-1", session=session)
|
||||
check_permission.assert_not_called()
|
||||
@@ -173,9 +171,9 @@ class TestDatasetMetadataGetApi:
|
||||
patch.object(DatasetService, "get_dataset_for_tenant", return_value=dataset),
|
||||
patch.object(DatasetService, "check_dataset_permission", side_effect=NoPermissionError),
|
||||
patch.object(MetadataService, "get_dataset_metadatas") as get_metadata,
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, MagicMock(), "tenant-1", current_user, dataset_id)
|
||||
method(api, MagicMock(), "tenant-1", current_user, dataset_id)
|
||||
|
||||
get_metadata.assert_not_called()
|
||||
|
||||
|
||||
@@ -111,9 +111,9 @@ class TestChatAudioApi:
|
||||
"transcript_asr",
|
||||
side_effect=audio_module.services.errors.app_model_config.AppModelConfigBrokenError(),
|
||||
),
|
||||
pytest.raises(AppUnavailableError),
|
||||
):
|
||||
with pytest.raises(AppUnavailableError):
|
||||
self.method(installed_app)
|
||||
self.method(installed_app)
|
||||
|
||||
def test_no_audio_uploaded(self, app: Flask, installed_app, audio_file):
|
||||
with (
|
||||
@@ -127,9 +127,9 @@ class TestChatAudioApi:
|
||||
"transcript_asr",
|
||||
side_effect=NoAudioUploadedServiceError(),
|
||||
),
|
||||
pytest.raises(NoAudioUploadedError),
|
||||
):
|
||||
with pytest.raises(NoAudioUploadedError):
|
||||
self.method(installed_app)
|
||||
self.method(installed_app)
|
||||
|
||||
def test_audio_too_large(self, app: Flask, installed_app, audio_file):
|
||||
with (
|
||||
@@ -143,9 +143,9 @@ class TestChatAudioApi:
|
||||
"transcript_asr",
|
||||
side_effect=AudioTooLargeServiceError("too big"),
|
||||
),
|
||||
pytest.raises(AudioTooLargeError),
|
||||
):
|
||||
with pytest.raises(AudioTooLargeError):
|
||||
self.method(installed_app)
|
||||
self.method(installed_app)
|
||||
|
||||
def test_provider_quota_exceeded(self, app: Flask, installed_app, audio_file):
|
||||
with (
|
||||
@@ -159,9 +159,9 @@ class TestChatAudioApi:
|
||||
"transcript_asr",
|
||||
side_effect=QuotaExceededError(),
|
||||
),
|
||||
pytest.raises(ProviderQuotaExceededError),
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
self.method(installed_app)
|
||||
self.method(installed_app)
|
||||
|
||||
def test_unknown_exception(self, app: Flask, installed_app, audio_file):
|
||||
with (
|
||||
@@ -175,9 +175,9 @@ class TestChatAudioApi:
|
||||
"transcript_asr",
|
||||
side_effect=Exception("boom"),
|
||||
),
|
||||
pytest.raises(InternalServerError),
|
||||
):
|
||||
with pytest.raises(InternalServerError):
|
||||
self.method(installed_app)
|
||||
self.method(installed_app)
|
||||
|
||||
def test_unsupported_audio_type(self, app: Flask, installed_app, audio_file):
|
||||
with (
|
||||
@@ -191,9 +191,9 @@ class TestChatAudioApi:
|
||||
"transcript_asr",
|
||||
side_effect=audio_module.UnsupportedAudioTypeServiceError(),
|
||||
),
|
||||
pytest.raises(audio_module.UnsupportedAudioTypeError),
|
||||
):
|
||||
with pytest.raises(audio_module.UnsupportedAudioTypeError):
|
||||
self.method(installed_app)
|
||||
self.method(installed_app)
|
||||
|
||||
def test_provider_not_support_speech_to_text(self, app: Flask, installed_app, audio_file):
|
||||
with (
|
||||
@@ -207,9 +207,9 @@ class TestChatAudioApi:
|
||||
"transcript_asr",
|
||||
side_effect=audio_module.ProviderNotSupportSpeechToTextServiceError(),
|
||||
),
|
||||
pytest.raises(audio_module.ProviderNotSupportSpeechToTextError),
|
||||
):
|
||||
with pytest.raises(audio_module.ProviderNotSupportSpeechToTextError):
|
||||
self.method(installed_app)
|
||||
self.method(installed_app)
|
||||
|
||||
def test_speech_to_text_disabled(self, app: Flask, installed_app, audio_file):
|
||||
with (
|
||||
@@ -223,9 +223,9 @@ class TestChatAudioApi:
|
||||
"transcript_asr",
|
||||
side_effect=SpeechToTextDisabledServiceError(),
|
||||
),
|
||||
pytest.raises(SpeechToTextDisabledError),
|
||||
):
|
||||
with pytest.raises(SpeechToTextDisabledError):
|
||||
self.method(installed_app)
|
||||
self.method(installed_app)
|
||||
|
||||
def test_provider_not_initialized(self, app: Flask, installed_app, audio_file):
|
||||
with (
|
||||
@@ -239,9 +239,9 @@ class TestChatAudioApi:
|
||||
"transcript_asr",
|
||||
side_effect=ProviderTokenNotInitError("not init"),
|
||||
),
|
||||
pytest.raises(ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
self.method(installed_app)
|
||||
self.method(installed_app)
|
||||
|
||||
def test_model_currently_not_supported(self, app: Flask, installed_app, audio_file):
|
||||
with (
|
||||
@@ -255,9 +255,9 @@ class TestChatAudioApi:
|
||||
"transcript_asr",
|
||||
side_effect=ModelCurrentlyNotSupportError(),
|
||||
),
|
||||
pytest.raises(ProviderModelCurrentlyNotSupportError),
|
||||
):
|
||||
with pytest.raises(ProviderModelCurrentlyNotSupportError):
|
||||
self.method(installed_app)
|
||||
self.method(installed_app)
|
||||
|
||||
def test_invoke_error_asr(self, app: Flask, installed_app, audio_file):
|
||||
with (
|
||||
@@ -271,9 +271,9 @@ class TestChatAudioApi:
|
||||
"transcript_asr",
|
||||
side_effect=InvokeError("invoke failed"),
|
||||
),
|
||||
pytest.raises(CompletionRequestError),
|
||||
):
|
||||
with pytest.raises(CompletionRequestError):
|
||||
self.method(installed_app)
|
||||
self.method(installed_app)
|
||||
|
||||
|
||||
class TestChatTextApi:
|
||||
@@ -321,9 +321,9 @@ class TestChatTextApi:
|
||||
"transcript_tts",
|
||||
side_effect=ProviderTokenNotInitError("not init"),
|
||||
),
|
||||
pytest.raises(ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
|
||||
def test_model_not_supported(self, app: Flask, installed_app):
|
||||
with (
|
||||
@@ -336,9 +336,9 @@ class TestChatTextApi:
|
||||
"transcript_tts",
|
||||
side_effect=ModelCurrentlyNotSupportError(),
|
||||
),
|
||||
pytest.raises(ProviderModelCurrentlyNotSupportError),
|
||||
):
|
||||
with pytest.raises(ProviderModelCurrentlyNotSupportError):
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
|
||||
def test_invoke_error(self, app: Flask, installed_app):
|
||||
with (
|
||||
@@ -351,9 +351,9 @@ class TestChatTextApi:
|
||||
"transcript_tts",
|
||||
side_effect=InvokeError("invoke failed"),
|
||||
),
|
||||
pytest.raises(CompletionRequestError),
|
||||
):
|
||||
with pytest.raises(CompletionRequestError):
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
|
||||
def test_unknown_exception(self, app: Flask, installed_app):
|
||||
with (
|
||||
@@ -366,9 +366,9 @@ class TestChatTextApi:
|
||||
"transcript_tts",
|
||||
side_effect=Exception("boom"),
|
||||
),
|
||||
pytest.raises(InternalServerError),
|
||||
):
|
||||
with pytest.raises(InternalServerError):
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
|
||||
def test_app_unavailable_tts(self, app: Flask, installed_app):
|
||||
with (
|
||||
@@ -381,9 +381,9 @@ class TestChatTextApi:
|
||||
"transcript_tts",
|
||||
side_effect=audio_module.services.errors.app_model_config.AppModelConfigBrokenError(),
|
||||
),
|
||||
pytest.raises(AppUnavailableError),
|
||||
):
|
||||
with pytest.raises(AppUnavailableError):
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
|
||||
def test_no_audio_uploaded_tts(self, app: Flask, installed_app):
|
||||
with (
|
||||
@@ -396,9 +396,9 @@ class TestChatTextApi:
|
||||
"transcript_tts",
|
||||
side_effect=NoAudioUploadedServiceError(),
|
||||
),
|
||||
pytest.raises(NoAudioUploadedError),
|
||||
):
|
||||
with pytest.raises(NoAudioUploadedError):
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
|
||||
def test_audio_too_large_tts(self, app: Flask, installed_app):
|
||||
with (
|
||||
@@ -411,9 +411,9 @@ class TestChatTextApi:
|
||||
"transcript_tts",
|
||||
side_effect=AudioTooLargeServiceError("too big"),
|
||||
),
|
||||
pytest.raises(AudioTooLargeError),
|
||||
):
|
||||
with pytest.raises(AudioTooLargeError):
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
|
||||
def test_unsupported_audio_type_tts(self, app: Flask, installed_app):
|
||||
with (
|
||||
@@ -426,9 +426,9 @@ class TestChatTextApi:
|
||||
"transcript_tts",
|
||||
side_effect=audio_module.UnsupportedAudioTypeServiceError(),
|
||||
),
|
||||
pytest.raises(audio_module.UnsupportedAudioTypeError),
|
||||
):
|
||||
with pytest.raises(audio_module.UnsupportedAudioTypeError):
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
|
||||
def test_provider_not_support_speech_to_text_tts(self, app: Flask, installed_app):
|
||||
with (
|
||||
@@ -441,9 +441,9 @@ class TestChatTextApi:
|
||||
"transcript_tts",
|
||||
side_effect=audio_module.ProviderNotSupportSpeechToTextServiceError(),
|
||||
),
|
||||
pytest.raises(audio_module.ProviderNotSupportSpeechToTextError),
|
||||
):
|
||||
with pytest.raises(audio_module.ProviderNotSupportSpeechToTextError):
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
|
||||
def test_quota_exceeded_tts(self, app: Flask, installed_app):
|
||||
with (
|
||||
@@ -456,6 +456,6 @@ class TestChatTextApi:
|
||||
"transcript_tts",
|
||||
side_effect=QuotaExceededError(),
|
||||
),
|
||||
pytest.raises(ProviderQuotaExceededError),
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
self.method(audio_module.TextToAudioPayload.model_validate({"text": "hi"}), installed_app)
|
||||
|
||||
@@ -140,15 +140,15 @@ class TestCompletionApi:
|
||||
"generate",
|
||||
side_effect=completion_module.services.errors.conversation.ConversationCompletedError(),
|
||||
),
|
||||
pytest.raises(ConversationCompletedError),
|
||||
):
|
||||
with pytest.raises(ConversationCompletedError):
|
||||
method(
|
||||
api,
|
||||
completion_module.CompletionMessageExplorePayload.model_validate(payload_data),
|
||||
_session(completion_app),
|
||||
user,
|
||||
completion_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.CompletionMessageExplorePayload.model_validate(payload_data),
|
||||
_session(completion_app),
|
||||
user,
|
||||
completion_app,
|
||||
)
|
||||
|
||||
def test_internal_error(self, app: Flask, completion_app, user, payload_patch, payload_data):
|
||||
api = completion_module.CompletionApi()
|
||||
@@ -162,15 +162,15 @@ class TestCompletionApi:
|
||||
"generate",
|
||||
side_effect=Exception("boom"),
|
||||
),
|
||||
pytest.raises(InternalServerError),
|
||||
):
|
||||
with pytest.raises(InternalServerError):
|
||||
method(
|
||||
api,
|
||||
completion_module.CompletionMessageExplorePayload.model_validate(payload_data),
|
||||
_session(completion_app),
|
||||
user,
|
||||
completion_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.CompletionMessageExplorePayload.model_validate(payload_data),
|
||||
_session(completion_app),
|
||||
user,
|
||||
completion_app,
|
||||
)
|
||||
|
||||
def test_conversation_not_exists(self, app: Flask, completion_app, user, payload_patch, payload_data):
|
||||
api = completion_module.CompletionApi()
|
||||
@@ -184,15 +184,15 @@ class TestCompletionApi:
|
||||
"generate",
|
||||
side_effect=completion_module.services.errors.conversation.ConversationNotExistsError(),
|
||||
),
|
||||
pytest.raises(completion_module.NotFound),
|
||||
):
|
||||
with pytest.raises(completion_module.NotFound):
|
||||
method(
|
||||
api,
|
||||
completion_module.CompletionMessageExplorePayload.model_validate(payload_data),
|
||||
_session(completion_app),
|
||||
user,
|
||||
completion_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.CompletionMessageExplorePayload.model_validate(payload_data),
|
||||
_session(completion_app),
|
||||
user,
|
||||
completion_app,
|
||||
)
|
||||
|
||||
def test_app_unavailable(self, app: Flask, completion_app, user, payload_patch, payload_data):
|
||||
api = completion_module.CompletionApi()
|
||||
@@ -206,15 +206,15 @@ class TestCompletionApi:
|
||||
"generate",
|
||||
side_effect=completion_module.services.errors.app_model_config.AppModelConfigBrokenError(),
|
||||
),
|
||||
pytest.raises(completion_module.AppUnavailableError),
|
||||
):
|
||||
with pytest.raises(completion_module.AppUnavailableError):
|
||||
method(
|
||||
api,
|
||||
completion_module.CompletionMessageExplorePayload.model_validate(payload_data),
|
||||
_session(completion_app),
|
||||
user,
|
||||
completion_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.CompletionMessageExplorePayload.model_validate(payload_data),
|
||||
_session(completion_app),
|
||||
user,
|
||||
completion_app,
|
||||
)
|
||||
|
||||
def test_provider_not_initialized(self, app: Flask, completion_app, user, payload_patch, payload_data):
|
||||
api = completion_module.CompletionApi()
|
||||
@@ -228,15 +228,15 @@ class TestCompletionApi:
|
||||
"generate",
|
||||
side_effect=completion_module.ProviderTokenNotInitError("not init"),
|
||||
),
|
||||
pytest.raises(completion_module.ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(completion_module.ProviderNotInitializeError):
|
||||
method(
|
||||
api,
|
||||
completion_module.CompletionMessageExplorePayload.model_validate(payload_data),
|
||||
_session(completion_app),
|
||||
user,
|
||||
completion_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.CompletionMessageExplorePayload.model_validate(payload_data),
|
||||
_session(completion_app),
|
||||
user,
|
||||
completion_app,
|
||||
)
|
||||
|
||||
def test_quota_exceeded(self, app: Flask, completion_app, user, payload_patch, payload_data):
|
||||
api = completion_module.CompletionApi()
|
||||
@@ -250,15 +250,15 @@ class TestCompletionApi:
|
||||
"generate",
|
||||
side_effect=completion_module.QuotaExceededError(),
|
||||
),
|
||||
pytest.raises(completion_module.ProviderQuotaExceededError),
|
||||
):
|
||||
with pytest.raises(completion_module.ProviderQuotaExceededError):
|
||||
method(
|
||||
api,
|
||||
completion_module.CompletionMessageExplorePayload.model_validate(payload_data),
|
||||
_session(completion_app),
|
||||
user,
|
||||
completion_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.CompletionMessageExplorePayload.model_validate(payload_data),
|
||||
_session(completion_app),
|
||||
user,
|
||||
completion_app,
|
||||
)
|
||||
|
||||
def test_model_not_supported(self, app: Flask, completion_app, user, payload_patch, payload_data):
|
||||
api = completion_module.CompletionApi()
|
||||
@@ -272,15 +272,15 @@ class TestCompletionApi:
|
||||
"generate",
|
||||
side_effect=completion_module.ModelCurrentlyNotSupportError(),
|
||||
),
|
||||
pytest.raises(completion_module.ProviderModelCurrentlyNotSupportError),
|
||||
):
|
||||
with pytest.raises(completion_module.ProviderModelCurrentlyNotSupportError):
|
||||
method(
|
||||
api,
|
||||
completion_module.CompletionMessageExplorePayload.model_validate(payload_data),
|
||||
_session(completion_app),
|
||||
user,
|
||||
completion_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.CompletionMessageExplorePayload.model_validate(payload_data),
|
||||
_session(completion_app),
|
||||
user,
|
||||
completion_app,
|
||||
)
|
||||
|
||||
def test_invoke_error(self, app: Flask, completion_app, user, payload_patch, payload_data):
|
||||
api = completion_module.CompletionApi()
|
||||
@@ -294,15 +294,15 @@ class TestCompletionApi:
|
||||
"generate",
|
||||
side_effect=completion_module.InvokeError("invoke failed"),
|
||||
),
|
||||
pytest.raises(completion_module.CompletionRequestError),
|
||||
):
|
||||
with pytest.raises(completion_module.CompletionRequestError):
|
||||
method(
|
||||
api,
|
||||
completion_module.CompletionMessageExplorePayload.model_validate(payload_data),
|
||||
_session(completion_app),
|
||||
user,
|
||||
completion_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.CompletionMessageExplorePayload.model_validate(payload_data),
|
||||
_session(completion_app),
|
||||
user,
|
||||
completion_app,
|
||||
)
|
||||
|
||||
|
||||
class TestCompletionStopApi:
|
||||
@@ -382,15 +382,15 @@ class TestChatApi:
|
||||
"generate",
|
||||
side_effect=InvokeRateLimitError("limit"),
|
||||
),
|
||||
pytest.raises(InvokeRateLimitHttpError),
|
||||
):
|
||||
with pytest.raises(InvokeRateLimitHttpError):
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
|
||||
def test_conversation_completed_chat(self, app: Flask, chat_app, user, payload_patch, payload_data):
|
||||
api = completion_module.ChatApi()
|
||||
@@ -404,15 +404,15 @@ class TestChatApi:
|
||||
"generate",
|
||||
side_effect=completion_module.services.errors.conversation.ConversationCompletedError(),
|
||||
),
|
||||
pytest.raises(ConversationCompletedError),
|
||||
):
|
||||
with pytest.raises(ConversationCompletedError):
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
|
||||
def test_conversation_not_exists_chat(self, app: Flask, chat_app, user, payload_patch, payload_data):
|
||||
api = completion_module.ChatApi()
|
||||
@@ -426,15 +426,15 @@ class TestChatApi:
|
||||
"generate",
|
||||
side_effect=completion_module.services.errors.conversation.ConversationNotExistsError(),
|
||||
),
|
||||
pytest.raises(completion_module.NotFound),
|
||||
):
|
||||
with pytest.raises(completion_module.NotFound):
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
|
||||
def test_invalid_conversation_id_fails_fast_as_not_found(self, app: Flask, chat_app, user) -> None:
|
||||
# A nonexistent conversation_id must fail fast as 404, before the streaming
|
||||
@@ -465,17 +465,17 @@ class TestChatApi:
|
||||
get_conversation_mock,
|
||||
),
|
||||
patch.object(completion_module.AppGenerateService, "generate", generate_mock),
|
||||
pytest.raises(completion_module.NotFound),
|
||||
):
|
||||
with pytest.raises(completion_module.NotFound):
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(
|
||||
{"inputs": {}, "query": "hi", "conversation_id": conversation_id}
|
||||
),
|
||||
session,
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(
|
||||
{"inputs": {}, "query": "hi", "conversation_id": conversation_id}
|
||||
),
|
||||
session,
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
|
||||
# The lookup must run before generation, so the generator is never started.
|
||||
generate_mock.assert_not_called()
|
||||
@@ -493,15 +493,15 @@ class TestChatApi:
|
||||
"generate",
|
||||
side_effect=completion_module.services.errors.app_model_config.AppModelConfigBrokenError(),
|
||||
),
|
||||
pytest.raises(completion_module.AppUnavailableError),
|
||||
):
|
||||
with pytest.raises(completion_module.AppUnavailableError):
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
|
||||
def test_provider_not_initialized_chat(self, app: Flask, chat_app, user, payload_patch, payload_data):
|
||||
api = completion_module.ChatApi()
|
||||
@@ -515,15 +515,15 @@ class TestChatApi:
|
||||
"generate",
|
||||
side_effect=completion_module.ProviderTokenNotInitError("not init"),
|
||||
),
|
||||
pytest.raises(completion_module.ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(completion_module.ProviderNotInitializeError):
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
|
||||
def test_quota_exceeded_chat(self, app: Flask, chat_app, user, payload_patch, payload_data):
|
||||
api = completion_module.ChatApi()
|
||||
@@ -537,15 +537,15 @@ class TestChatApi:
|
||||
"generate",
|
||||
side_effect=completion_module.QuotaExceededError(),
|
||||
),
|
||||
pytest.raises(completion_module.ProviderQuotaExceededError),
|
||||
):
|
||||
with pytest.raises(completion_module.ProviderQuotaExceededError):
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
|
||||
def test_model_not_supported_chat(self, app: Flask, chat_app, user, payload_patch, payload_data):
|
||||
api = completion_module.ChatApi()
|
||||
@@ -559,15 +559,15 @@ class TestChatApi:
|
||||
"generate",
|
||||
side_effect=completion_module.ModelCurrentlyNotSupportError(),
|
||||
),
|
||||
pytest.raises(completion_module.ProviderModelCurrentlyNotSupportError),
|
||||
):
|
||||
with pytest.raises(completion_module.ProviderModelCurrentlyNotSupportError):
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
|
||||
def test_invoke_error_chat(self, app: Flask, chat_app, user, payload_patch, payload_data):
|
||||
api = completion_module.ChatApi()
|
||||
@@ -581,15 +581,15 @@ class TestChatApi:
|
||||
"generate",
|
||||
side_effect=completion_module.InvokeError("invoke failed"),
|
||||
),
|
||||
pytest.raises(completion_module.CompletionRequestError),
|
||||
):
|
||||
with pytest.raises(completion_module.CompletionRequestError):
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
|
||||
def test_internal_error_chat(self, app: Flask, chat_app, user, payload_patch, payload_data):
|
||||
api = completion_module.ChatApi()
|
||||
@@ -603,15 +603,15 @@ class TestChatApi:
|
||||
"generate",
|
||||
side_effect=Exception("boom"),
|
||||
),
|
||||
pytest.raises(InternalServerError),
|
||||
):
|
||||
with pytest.raises(InternalServerError):
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
method(
|
||||
api,
|
||||
completion_module.ChatMessagePayload.model_validate(payload_data),
|
||||
_session(chat_app),
|
||||
user,
|
||||
chat_app,
|
||||
)
|
||||
|
||||
|
||||
class TestChatStopApi:
|
||||
|
||||
@@ -165,9 +165,9 @@ class TestMessageListApi(_UsesSQLiteSession):
|
||||
"pagination_by_first_id",
|
||||
side_effect=ConversationNotExistsError(),
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(self.account, installed_app)
|
||||
method(self.account, installed_app)
|
||||
|
||||
def test_first_message_not_exists(self, app: Flask):
|
||||
api = module.MessageListApi()
|
||||
@@ -185,9 +185,9 @@ class TestMessageListApi(_UsesSQLiteSession):
|
||||
"pagination_by_first_id",
|
||||
side_effect=FirstMessageNotExistsError(),
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(self.account, installed_app)
|
||||
method(self.account, installed_app)
|
||||
|
||||
|
||||
class TestMessageFeedbackApi(_UsesSQLiteSession):
|
||||
@@ -223,9 +223,9 @@ class TestMessageFeedbackApi(_UsesSQLiteSession):
|
||||
"create_feedback",
|
||||
side_effect=MessageNotExistsError(),
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(module.MessageFeedbackPayload.model_validate({}), self.account, installed_app, "mid")
|
||||
method(module.MessageFeedbackPayload.model_validate({}), self.account, installed_app, "mid")
|
||||
|
||||
|
||||
class TestMessageMoreLikeThisApi(_UsesSQLiteSession):
|
||||
@@ -280,9 +280,9 @@ class TestMessageMoreLikeThisApi(_UsesSQLiteSession):
|
||||
"generate_more_like_this",
|
||||
side_effect=module.MoreLikeThisDisabledError(),
|
||||
),
|
||||
pytest.raises(AppMoreLikeThisDisabledError),
|
||||
):
|
||||
with pytest.raises(AppMoreLikeThisDisabledError):
|
||||
method(self.sqlite_session, self.account, installed_app, "mid")
|
||||
method(self.sqlite_session, self.account, installed_app, "mid")
|
||||
|
||||
def test_message_not_exists_more_like_this(self, app: Flask):
|
||||
api = module.MessageMoreLikeThisApi()
|
||||
@@ -300,9 +300,9 @@ class TestMessageMoreLikeThisApi(_UsesSQLiteSession):
|
||||
"generate_more_like_this",
|
||||
side_effect=MessageNotExistsError(),
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(self.sqlite_session, self.account, installed_app, "mid")
|
||||
method(self.sqlite_session, self.account, installed_app, "mid")
|
||||
|
||||
def test_provider_not_init_more_like_this(self, app: Flask):
|
||||
api = module.MessageMoreLikeThisApi()
|
||||
@@ -320,9 +320,9 @@ class TestMessageMoreLikeThisApi(_UsesSQLiteSession):
|
||||
"generate_more_like_this",
|
||||
side_effect=ProviderTokenNotInitError("test"),
|
||||
),
|
||||
pytest.raises(ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(self.sqlite_session, self.account, installed_app, "mid")
|
||||
method(self.sqlite_session, self.account, installed_app, "mid")
|
||||
|
||||
def test_quota_exceeded_more_like_this(self, app: Flask):
|
||||
api = module.MessageMoreLikeThisApi()
|
||||
@@ -340,9 +340,9 @@ class TestMessageMoreLikeThisApi(_UsesSQLiteSession):
|
||||
"generate_more_like_this",
|
||||
side_effect=QuotaExceededError(),
|
||||
),
|
||||
pytest.raises(ProviderQuotaExceededError),
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
method(self.sqlite_session, self.account, installed_app, "mid")
|
||||
method(self.sqlite_session, self.account, installed_app, "mid")
|
||||
|
||||
def test_model_not_support_more_like_this(self, app: Flask):
|
||||
api = module.MessageMoreLikeThisApi()
|
||||
@@ -360,9 +360,9 @@ class TestMessageMoreLikeThisApi(_UsesSQLiteSession):
|
||||
"generate_more_like_this",
|
||||
side_effect=ModelCurrentlyNotSupportError(),
|
||||
),
|
||||
pytest.raises(ProviderModelCurrentlyNotSupportError),
|
||||
):
|
||||
with pytest.raises(ProviderModelCurrentlyNotSupportError):
|
||||
method(self.sqlite_session, self.account, installed_app, "mid")
|
||||
method(self.sqlite_session, self.account, installed_app, "mid")
|
||||
|
||||
def test_invoke_error_more_like_this(self, app: Flask):
|
||||
api = module.MessageMoreLikeThisApi()
|
||||
@@ -380,9 +380,9 @@ class TestMessageMoreLikeThisApi(_UsesSQLiteSession):
|
||||
"generate_more_like_this",
|
||||
side_effect=InvokeError("test error"),
|
||||
),
|
||||
pytest.raises(CompletionRequestError),
|
||||
):
|
||||
with pytest.raises(CompletionRequestError):
|
||||
method(self.sqlite_session, self.account, installed_app, "mid")
|
||||
method(self.sqlite_session, self.account, installed_app, "mid")
|
||||
|
||||
def test_unexpected_error_more_like_this(self, app: Flask):
|
||||
api = module.MessageMoreLikeThisApi()
|
||||
@@ -400,9 +400,9 @@ class TestMessageMoreLikeThisApi(_UsesSQLiteSession):
|
||||
"generate_more_like_this",
|
||||
side_effect=Exception("unexpected"),
|
||||
),
|
||||
pytest.raises(InternalServerError),
|
||||
):
|
||||
with pytest.raises(InternalServerError):
|
||||
method(self.sqlite_session, self.account, installed_app, "mid")
|
||||
method(self.sqlite_session, self.account, installed_app, "mid")
|
||||
|
||||
|
||||
class TestMessageSuggestedQuestionApi(_UsesSQLiteSession):
|
||||
@@ -444,9 +444,9 @@ class TestMessageSuggestedQuestionApi(_UsesSQLiteSession):
|
||||
"get_suggested_questions_after_answer",
|
||||
side_effect=SuggestedQuestionsAfterAnswerDisabledError(),
|
||||
),
|
||||
pytest.raises(AppSuggestedQuestionsAfterAnswerDisabledError),
|
||||
):
|
||||
with pytest.raises(AppSuggestedQuestionsAfterAnswerDisabledError):
|
||||
method(self.account, installed_app, "mid")
|
||||
method(self.account, installed_app, "mid")
|
||||
|
||||
def test_message_not_exists_suggested_question(self):
|
||||
api = module.MessageSuggestedQuestionApi()
|
||||
@@ -460,9 +460,9 @@ class TestMessageSuggestedQuestionApi(_UsesSQLiteSession):
|
||||
"get_suggested_questions_after_answer",
|
||||
side_effect=MessageNotExistsError(),
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(self.account, installed_app, "mid")
|
||||
method(self.account, installed_app, "mid")
|
||||
|
||||
def test_conversation_not_exists_suggested_question(self):
|
||||
api = module.MessageSuggestedQuestionApi()
|
||||
@@ -476,9 +476,9 @@ class TestMessageSuggestedQuestionApi(_UsesSQLiteSession):
|
||||
"get_suggested_questions_after_answer",
|
||||
side_effect=ConversationNotExistsError(),
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(self.account, installed_app, "mid")
|
||||
method(self.account, installed_app, "mid")
|
||||
|
||||
def test_provider_not_init_suggested_question(self):
|
||||
api = module.MessageSuggestedQuestionApi()
|
||||
@@ -492,9 +492,9 @@ class TestMessageSuggestedQuestionApi(_UsesSQLiteSession):
|
||||
"get_suggested_questions_after_answer",
|
||||
side_effect=ProviderTokenNotInitError("test"),
|
||||
),
|
||||
pytest.raises(ProviderNotInitializeError),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(self.account, installed_app, "mid")
|
||||
method(self.account, installed_app, "mid")
|
||||
|
||||
def test_quota_exceeded_suggested_question(self):
|
||||
api = module.MessageSuggestedQuestionApi()
|
||||
@@ -508,9 +508,9 @@ class TestMessageSuggestedQuestionApi(_UsesSQLiteSession):
|
||||
"get_suggested_questions_after_answer",
|
||||
side_effect=QuotaExceededError(),
|
||||
),
|
||||
pytest.raises(ProviderQuotaExceededError),
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
method(self.account, installed_app, "mid")
|
||||
method(self.account, installed_app, "mid")
|
||||
|
||||
def test_model_not_support_suggested_question(self):
|
||||
api = module.MessageSuggestedQuestionApi()
|
||||
@@ -524,9 +524,9 @@ class TestMessageSuggestedQuestionApi(_UsesSQLiteSession):
|
||||
"get_suggested_questions_after_answer",
|
||||
side_effect=ModelCurrentlyNotSupportError(),
|
||||
),
|
||||
pytest.raises(ProviderModelCurrentlyNotSupportError),
|
||||
):
|
||||
with pytest.raises(ProviderModelCurrentlyNotSupportError):
|
||||
method(self.account, installed_app, "mid")
|
||||
method(self.account, installed_app, "mid")
|
||||
|
||||
def test_invoke_error_suggested_question(self):
|
||||
api = module.MessageSuggestedQuestionApi()
|
||||
@@ -540,9 +540,9 @@ class TestMessageSuggestedQuestionApi(_UsesSQLiteSession):
|
||||
"get_suggested_questions_after_answer",
|
||||
side_effect=InvokeError("test error"),
|
||||
),
|
||||
pytest.raises(CompletionRequestError),
|
||||
):
|
||||
with pytest.raises(CompletionRequestError):
|
||||
method(self.account, installed_app, "mid")
|
||||
method(self.account, installed_app, "mid")
|
||||
|
||||
def test_unexpected_error_suggested_question(self):
|
||||
api = module.MessageSuggestedQuestionApi()
|
||||
@@ -556,6 +556,6 @@ class TestMessageSuggestedQuestionApi(_UsesSQLiteSession):
|
||||
"get_suggested_questions_after_answer",
|
||||
side_effect=Exception("unexpected"),
|
||||
),
|
||||
pytest.raises(InternalServerError),
|
||||
):
|
||||
with pytest.raises(InternalServerError):
|
||||
method(self.account, installed_app, "mid")
|
||||
method(self.account, installed_app, "mid")
|
||||
|
||||
@@ -128,9 +128,9 @@ class TestRecommendedAppApi:
|
||||
"application_services",
|
||||
return_value=SimpleNamespace(recommended_app_queries=queries),
|
||||
),
|
||||
pytest.raises(module.RecommendedAppNotFoundError) as exc_info,
|
||||
):
|
||||
with pytest.raises(module.RecommendedAppNotFoundError) as exc_info:
|
||||
method(api, "11111111-1111-1111-1111-111111111111")
|
||||
method(api, "11111111-1111-1111-1111-111111111111")
|
||||
|
||||
assert exc_info.value.data == {
|
||||
"code": "recommended_app_not_found",
|
||||
|
||||
@@ -186,9 +186,9 @@ class TestSavedMessageListApi(_UsesSQLiteSession):
|
||||
"save",
|
||||
side_effect=MessageNotExistsError(),
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, module.SavedMessageCreatePayload.model_validate(payload), self.account, installed_app)
|
||||
method(api, module.SavedMessageCreatePayload.model_validate(payload), self.account, installed_app)
|
||||
|
||||
|
||||
class TestSavedMessageApi(_UsesSQLiteSession):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -60,15 +60,14 @@ class TestInstalledAppWorkflowRunApi:
|
||||
installed_app = make_installed_app(sqlite_session, mode=AppMode.CHAT)
|
||||
user = Account(name="User", email="user@example.com")
|
||||
|
||||
with app.test_request_context("/"):
|
||||
with pytest.raises(NotWorkflowAppError):
|
||||
method(
|
||||
api,
|
||||
WorkflowRunPayload.model_validate({"inputs": {}}),
|
||||
sqlite_session,
|
||||
user,
|
||||
installed_app,
|
||||
)
|
||||
with app.test_request_context("/"), pytest.raises(NotWorkflowAppError):
|
||||
method(
|
||||
api,
|
||||
WorkflowRunPayload.model_validate({"inputs": {}}),
|
||||
sqlite_session,
|
||||
user,
|
||||
installed_app,
|
||||
)
|
||||
|
||||
def test_success(self, app: Flask, sqlite_session: Session, payload):
|
||||
api = InstalledAppWorkflowRunApi()
|
||||
@@ -103,9 +102,9 @@ class TestInstalledAppWorkflowRunApi:
|
||||
"controllers.console.explore.workflow.AppGenerateService.generate",
|
||||
side_effect=InvokeRateLimitError("rate limit"),
|
||||
),
|
||||
pytest.raises(InvokeRateLimitHttpError),
|
||||
):
|
||||
with pytest.raises(InvokeRateLimitHttpError):
|
||||
method(api, req_data, sqlite_session, user, installed_app)
|
||||
method(api, req_data, sqlite_session, user, installed_app)
|
||||
|
||||
def test_unexpected_exception(self, app: Flask, sqlite_session: Session, payload):
|
||||
api = InstalledAppWorkflowRunApi()
|
||||
@@ -120,9 +119,9 @@ class TestInstalledAppWorkflowRunApi:
|
||||
"controllers.console.explore.workflow.AppGenerateService.generate",
|
||||
side_effect=Exception("boom"),
|
||||
),
|
||||
pytest.raises(InternalServerError),
|
||||
):
|
||||
with pytest.raises(InternalServerError):
|
||||
method(api, req_data, sqlite_session, user, installed_app)
|
||||
method(api, req_data, sqlite_session, user, installed_app)
|
||||
|
||||
|
||||
class TestInstalledAppWorkflowTaskStopApi:
|
||||
|
||||
@@ -72,12 +72,14 @@ def test_installed_app_required_not_found(
|
||||
def view(installed_app):
|
||||
return "ok"
|
||||
|
||||
with patch(
|
||||
"controllers.console.explore.wraps.current_account_with_tenant",
|
||||
return_value=(_account(), tenant_id),
|
||||
with (
|
||||
patch(
|
||||
"controllers.console.explore.wraps.current_account_with_tenant",
|
||||
return_value=(_account(), tenant_id),
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
view(str(uuid4()))
|
||||
view(str(uuid4()))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sqlite_session", [(InstalledApp, App)], indirect=True)
|
||||
@@ -96,12 +98,14 @@ def test_installed_app_required_app_deleted(
|
||||
def view(installed_app):
|
||||
return "ok"
|
||||
|
||||
with patch(
|
||||
"controllers.console.explore.wraps.current_account_with_tenant",
|
||||
return_value=(_account(), tenant_id),
|
||||
with (
|
||||
patch(
|
||||
"controllers.console.explore.wraps.current_account_with_tenant",
|
||||
return_value=(_account(), tenant_id),
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
view(installed_app_id)
|
||||
view(installed_app_id)
|
||||
|
||||
assert sqlite_session.get(InstalledApp, installed_app_id) is None
|
||||
|
||||
@@ -152,9 +156,9 @@ def test_user_allowed_to_access_app_denied():
|
||||
"controllers.console.explore.wraps.EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp",
|
||||
return_value=False,
|
||||
),
|
||||
pytest.raises(AppAccessDeniedError),
|
||||
):
|
||||
with pytest.raises(AppAccessDeniedError):
|
||||
view(installed_app)
|
||||
view(installed_app)
|
||||
|
||||
|
||||
def test_user_allowed_to_access_app_success():
|
||||
@@ -192,12 +196,14 @@ def test_trial_app_required_not_allowed(
|
||||
def view(app):
|
||||
return "ok"
|
||||
|
||||
with patch(
|
||||
"controllers.console.explore.wraps.current_account_with_tenant",
|
||||
return_value=(_account(account_id=str(uuid4())), None),
|
||||
with (
|
||||
patch(
|
||||
"controllers.console.explore.wraps.current_account_with_tenant",
|
||||
return_value=(_account(account_id=str(uuid4())), None),
|
||||
),
|
||||
pytest.raises(TrialAppNotAllowed),
|
||||
):
|
||||
with pytest.raises(TrialAppNotAllowed):
|
||||
view(str(uuid4()))
|
||||
view(str(uuid4()))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sqlite_session", [(TrialApp, App, AccountTrialAppRecord)], indirect=True)
|
||||
@@ -217,12 +223,14 @@ def test_trial_app_required_limit_exceeded(
|
||||
def view(app):
|
||||
return "ok"
|
||||
|
||||
with patch(
|
||||
"controllers.console.explore.wraps.current_account_with_tenant",
|
||||
return_value=(_account(account_id=account_id), None),
|
||||
with (
|
||||
patch(
|
||||
"controllers.console.explore.wraps.current_account_with_tenant",
|
||||
return_value=(_account(account_id=account_id), None),
|
||||
),
|
||||
pytest.raises(TrialAppLimitExceeded),
|
||||
):
|
||||
with pytest.raises(TrialAppLimitExceeded):
|
||||
view(app.id)
|
||||
view(app.id)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sqlite_session", [(TrialApp, App, AccountTrialAppRecord)], indirect=True)
|
||||
@@ -258,9 +266,11 @@ def test_trial_feature_enable_disabled():
|
||||
|
||||
services = MagicMock()
|
||||
services.recommended_app_queries.is_trial_enabled.return_value = False
|
||||
with patch("controllers.console.explore.wraps.application_services", return_value=services):
|
||||
with pytest.raises(TrialAppFeatureDisabledError) as exc_info:
|
||||
view()
|
||||
with (
|
||||
patch("controllers.console.explore.wraps.application_services", return_value=services),
|
||||
pytest.raises(TrialAppFeatureDisabledError) as exc_info,
|
||||
):
|
||||
view()
|
||||
|
||||
assert exc_info.value.data == {
|
||||
"code": "trial_app_feature_disabled",
|
||||
|
||||
@@ -82,9 +82,8 @@ def test_get_snippet_requires_snippet_id(app):
|
||||
def view(**kwargs):
|
||||
return kwargs
|
||||
|
||||
with app.test_request_context("/snippets"):
|
||||
with pytest.raises(ValueError, match="missing snippet_id"):
|
||||
view()
|
||||
with app.test_request_context("/snippets"), pytest.raises(ValueError, match="missing snippet_id"):
|
||||
view()
|
||||
|
||||
|
||||
def test_get_snippet_injects_resolved_snippet(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -119,9 +118,8 @@ def test_get_snippet_raises_not_found_when_snippet_missing(app: Flask, monkeypat
|
||||
)
|
||||
monkeypatch.setattr(snippet_workflow_module.SnippetService, "get_snippet_by_id", Mock(return_value=None))
|
||||
|
||||
with app.test_request_context("/snippets/snippet-1"):
|
||||
with pytest.raises(NotFound, match="Snippet not found"):
|
||||
view(snippet_id="snippet-1")
|
||||
with app.test_request_context("/snippets/snippet-1"), pytest.raises(NotFound, match="Snippet not found"):
|
||||
view(snippet_id="snippet-1")
|
||||
|
||||
|
||||
def test_draft_workflow_get_raises_when_missing(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -135,9 +133,11 @@ def test_draft_workflow_get_raises_when_missing(app: Flask, monkeypatch: pytest.
|
||||
api = snippet_workflow_module.SnippetDraftWorkflowApi()
|
||||
handler = unwrap(api.get)
|
||||
|
||||
with app.test_request_context("/snippets/snippet-1/workflows/draft"):
|
||||
with pytest.raises(snippet_workflow_module.DraftWorkflowNotExist):
|
||||
handler(api, snippet=snippet)
|
||||
with (
|
||||
app.test_request_context("/snippets/snippet-1/workflows/draft"),
|
||||
pytest.raises(snippet_workflow_module.DraftWorkflowNotExist),
|
||||
):
|
||||
handler(api, snippet=snippet)
|
||||
|
||||
|
||||
def test_draft_workflow_get_uses_session_aware_response_source(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -393,12 +393,14 @@ def test_restore_published_snippet_workflow_to_draft_not_found(app: Flask, monke
|
||||
api = snippet_workflow_module.SnippetDraftWorkflowRestoreApi()
|
||||
handler = unwrap(api.post)
|
||||
|
||||
with app.test_request_context(
|
||||
"/snippets/snippet-1/workflows/published-workflow/restore",
|
||||
method="POST",
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/snippets/snippet-1/workflows/published-workflow/restore",
|
||||
method="POST",
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
handler(api, user, snippet, workflow_id="published-workflow")
|
||||
handler(api, user, snippet, workflow_id="published-workflow")
|
||||
|
||||
|
||||
def test_restore_published_snippet_workflow_to_draft_returns_400_for_draft_source(
|
||||
@@ -420,12 +422,14 @@ def test_restore_published_snippet_workflow_to_draft_returns_400_for_draft_sourc
|
||||
api = snippet_workflow_module.SnippetDraftWorkflowRestoreApi()
|
||||
handler = unwrap(api.post)
|
||||
|
||||
with app.test_request_context(
|
||||
"/snippets/snippet-1/workflows/draft-workflow/restore",
|
||||
method="POST",
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/snippets/snippet-1/workflows/draft-workflow/restore",
|
||||
method="POST",
|
||||
),
|
||||
pytest.raises(HTTPException) as exc,
|
||||
):
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
handler(api, user, snippet, workflow_id="draft-workflow")
|
||||
handler(api, user, snippet, workflow_id="draft-workflow")
|
||||
|
||||
assert exc.value.code == 400
|
||||
assert exc.value.description == snippet_workflow_module.RESTORE_SOURCE_WORKFLOW_MUST_BE_PUBLISHED_MESSAGE
|
||||
@@ -450,12 +454,14 @@ def test_restore_published_snippet_workflow_to_draft_returns_400_for_invalid_gra
|
||||
api = snippet_workflow_module.SnippetDraftWorkflowRestoreApi()
|
||||
handler = unwrap(api.post)
|
||||
|
||||
with app.test_request_context(
|
||||
"/snippets/snippet-1/workflows/published-workflow/restore",
|
||||
method="POST",
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/snippets/snippet-1/workflows/published-workflow/restore",
|
||||
method="POST",
|
||||
),
|
||||
pytest.raises(HTTPException) as exc,
|
||||
):
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
handler(api, user, snippet, workflow_id="published-workflow")
|
||||
handler(api, user, snippet, workflow_id="published-workflow")
|
||||
|
||||
assert exc.value.code == 400
|
||||
assert exc.value.description == "invalid snippet workflow graph"
|
||||
@@ -559,19 +565,21 @@ def test_update_published_snippet_workflow_raises_not_found(
|
||||
api = snippet_workflow_module.SnippetWorkflowByIdApi()
|
||||
handler = unwrap(api.patch)
|
||||
|
||||
with app.test_request_context(
|
||||
"/snippets/snippet-1/workflows/missing-workflow",
|
||||
method="PATCH",
|
||||
json={"marked_name": "v1"},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/snippets/snippet-1/workflows/missing-workflow",
|
||||
method="PATCH",
|
||||
json={"marked_name": "v1"},
|
||||
),
|
||||
pytest.raises(NotFound, match="Workflow not found"),
|
||||
):
|
||||
with pytest.raises(NotFound, match="Workflow not found"):
|
||||
handler(
|
||||
api,
|
||||
snippet_workflow_module.WorkflowUpdatePayload.model_validate({"marked_name": "v1"}),
|
||||
user,
|
||||
snippet,
|
||||
workflow_id="missing-workflow",
|
||||
)
|
||||
handler(
|
||||
api,
|
||||
snippet_workflow_module.WorkflowUpdatePayload.model_validate({"marked_name": "v1"}),
|
||||
user,
|
||||
snippet,
|
||||
workflow_id="missing-workflow",
|
||||
)
|
||||
|
||||
sqlite_session.refresh(snippet)
|
||||
assert snippet.name == "Snippet"
|
||||
@@ -614,9 +622,11 @@ def test_delete_published_snippet_workflow_raises_not_found(app: Flask, monkeypa
|
||||
api = snippet_workflow_module.SnippetWorkflowByIdApi()
|
||||
handler = unwrap(api.delete)
|
||||
|
||||
with app.test_request_context("/snippets/snippet-1/workflows/missing-workflow", method="DELETE"):
|
||||
with pytest.raises(NotFound):
|
||||
handler(api, _snippet(), workflow_id="missing-workflow")
|
||||
with (
|
||||
app.test_request_context("/snippets/snippet-1/workflows/missing-workflow", method="DELETE"),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
handler(api, _snippet(), workflow_id="missing-workflow")
|
||||
|
||||
|
||||
def test_delete_published_snippet_workflow_raises_bad_request_when_in_use(
|
||||
@@ -634,9 +644,11 @@ def test_delete_published_snippet_workflow_raises_bad_request_when_in_use(
|
||||
api = snippet_workflow_module.SnippetWorkflowByIdApi()
|
||||
handler = unwrap(api.delete)
|
||||
|
||||
with app.test_request_context("/snippets/snippet-1/workflows/workflow-1", method="DELETE"):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
handler(api, _snippet(), workflow_id="workflow-1")
|
||||
with (
|
||||
app.test_request_context("/snippets/snippet-1/workflows/workflow-1", method="DELETE"),
|
||||
pytest.raises(HTTPException) as exc_info,
|
||||
):
|
||||
handler(api, _snippet(), workflow_id="workflow-1")
|
||||
|
||||
assert exc_info.value.code == 400
|
||||
|
||||
@@ -652,9 +664,11 @@ def test_workflow_run_detail_raises_not_found_when_run_missing(app: Flask, monke
|
||||
api = snippet_workflow_module.SnippetWorkflowRunDetailApi()
|
||||
handler = unwrap(api.get)
|
||||
|
||||
with app.test_request_context("/snippets/snippet-1/workflow-runs/run-1"):
|
||||
with pytest.raises(NotFound, match="Workflow run not found"):
|
||||
handler(api, snippet=snippet, run_id="run-1")
|
||||
with (
|
||||
app.test_request_context("/snippets/snippet-1/workflow-runs/run-1"),
|
||||
pytest.raises(NotFound, match="Workflow run not found"),
|
||||
):
|
||||
handler(api, snippet=snippet, run_id="run-1")
|
||||
|
||||
|
||||
def test_draft_node_last_run_raises_not_found_when_execution_missing(
|
||||
@@ -674,9 +688,11 @@ def test_draft_node_last_run_raises_not_found_when_execution_missing(
|
||||
api = snippet_workflow_module.SnippetDraftNodeLastRunApi()
|
||||
handler = unwrap(api.get)
|
||||
|
||||
with app.test_request_context("/snippets/snippet-1/workflows/draft/nodes/llm-1/last-run"):
|
||||
with pytest.raises(NotFound, match="Node last run not found"):
|
||||
handler(api, snippet=snippet, node_id="llm-1")
|
||||
with (
|
||||
app.test_request_context("/snippets/snippet-1/workflows/draft/nodes/llm-1/last-run"),
|
||||
pytest.raises(NotFound, match="Node last run not found"),
|
||||
):
|
||||
handler(api, snippet=snippet, node_id="llm-1")
|
||||
|
||||
|
||||
def test_workflow_task_stop_uses_queue_flag_and_graph_command(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
||||
@@ -188,9 +188,8 @@ def test_variable_collection_get_raises_when_draft_workflow_missing(
|
||||
api = module.SnippetWorkflowVariableCollectionApi()
|
||||
handler = unwrap(api.get)
|
||||
|
||||
with app.test_request_context("/?page=1&limit=20"):
|
||||
with pytest.raises(module.DraftWorkflowNotExist):
|
||||
handler(api, _make_account(), snippet=SimpleNamespace(id="snippet-1"))
|
||||
with app.test_request_context("/?page=1&limit=20"), pytest.raises(module.DraftWorkflowNotExist):
|
||||
handler(api, _make_account(), snippet=SimpleNamespace(id="snippet-1"))
|
||||
|
||||
|
||||
def test_node_variable_collection_get_lists_persisted_node_variables(
|
||||
|
||||
@@ -83,9 +83,8 @@ class TestTagListApi:
|
||||
def get(self, req_data: TagListQueryParam) -> TagListQueryParam:
|
||||
return req_data
|
||||
|
||||
with app.test_request_context(url, method="GET"):
|
||||
with pytest.raises(UnprocessableEntity):
|
||||
Handler().get()
|
||||
with app.test_request_context(url, method="GET"), pytest.raises(UnprocessableEntity):
|
||||
Handler().get()
|
||||
|
||||
def test_get_uses_application_service(
|
||||
self, app: Flask, request_context: RequestContext, tags_service: MagicMock
|
||||
@@ -189,13 +188,13 @@ class TestTagListApi:
|
||||
app.test_request_context("/"),
|
||||
config_overrides_context(RBAC_ENABLED=False),
|
||||
patch.object(module, "current_account_with_tenant", return_value=(readonly, "tenant-1")),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
unwrap(TagListApi().post)(
|
||||
TagListApi(),
|
||||
TagBasePayload(name="Tag", type=TagType.KNOWLEDGE),
|
||||
request_context,
|
||||
)
|
||||
unwrap(TagListApi().post)(
|
||||
TagListApi(),
|
||||
TagBasePayload(name="Tag", type=TagType.KNOWLEDGE),
|
||||
request_context,
|
||||
)
|
||||
|
||||
def test_post_maps_name_conflict_to_legacy_value_error(
|
||||
self, app: Flask, request_context: RequestContext, tags_service: MagicMock
|
||||
@@ -207,13 +206,13 @@ class TestTagListApi:
|
||||
app.test_request_context("/"),
|
||||
config_overrides_context(RBAC_ENABLED=False),
|
||||
patch.object(module, "current_account_with_tenant", return_value=(owner, "tenant-1")),
|
||||
pytest.raises(ValueError, match="Tag name already exists") as exc_info,
|
||||
):
|
||||
with pytest.raises(ValueError, match="Tag name already exists") as exc_info:
|
||||
unwrap(TagListApi().post)(
|
||||
TagListApi(),
|
||||
TagBasePayload(name="Tag", type=TagType.KNOWLEDGE),
|
||||
request_context,
|
||||
)
|
||||
unwrap(TagListApi().post)(
|
||||
TagListApi(),
|
||||
TagBasePayload(name="Tag", type=TagType.KNOWLEDGE),
|
||||
request_context,
|
||||
)
|
||||
|
||||
assert exc_info.value.__cause__ is None
|
||||
assert exc_info.value.__suppress_context__ is True
|
||||
@@ -228,13 +227,13 @@ class TestTagListApi:
|
||||
app.test_request_context("/"),
|
||||
config_overrides_context(RBAC_ENABLED=False),
|
||||
patch.object(module, "current_account_with_tenant", return_value=(owner, "tenant-1")),
|
||||
pytest.raises(TagApplicationError, match="unexpected"),
|
||||
):
|
||||
with pytest.raises(TagApplicationError, match="unexpected"):
|
||||
unwrap(TagListApi().post)(
|
||||
TagListApi(),
|
||||
TagBasePayload(name="Tag", type=TagType.KNOWLEDGE),
|
||||
request_context,
|
||||
)
|
||||
unwrap(TagListApi().post)(
|
||||
TagListApi(),
|
||||
TagBasePayload(name="Tag", type=TagType.KNOWLEDGE),
|
||||
request_context,
|
||||
)
|
||||
|
||||
|
||||
class TestTagUpdateDeleteApi:
|
||||
@@ -276,14 +275,14 @@ class TestTagUpdateDeleteApi:
|
||||
app.test_request_context("/"),
|
||||
config_overrides_context(RBAC_ENABLED=False),
|
||||
patch.object(module, "current_account_with_tenant", return_value=(readonly, "tenant-1")),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
unwrap(TagUpdateDeleteApi().patch)(
|
||||
TagUpdateDeleteApi(),
|
||||
TagUpdateRequestPayload(name="Updated"),
|
||||
request_context,
|
||||
"tag-1",
|
||||
)
|
||||
unwrap(TagUpdateDeleteApi().patch)(
|
||||
TagUpdateDeleteApi(),
|
||||
TagUpdateRequestPayload(name="Updated"),
|
||||
request_context,
|
||||
"tag-1",
|
||||
)
|
||||
|
||||
def test_patch_maps_missing_tag_to_not_found(
|
||||
self, app: Flask, request_context: RequestContext, tags_service: MagicMock
|
||||
@@ -295,14 +294,14 @@ class TestTagUpdateDeleteApi:
|
||||
app.test_request_context("/"),
|
||||
config_overrides_context(RBAC_ENABLED=False),
|
||||
patch.object(module, "current_account_with_tenant", return_value=(owner, "tenant-1")),
|
||||
pytest.raises(NotFound, match="Tag not found") as exc_info,
|
||||
):
|
||||
with pytest.raises(NotFound, match="Tag not found") as exc_info:
|
||||
unwrap(TagUpdateDeleteApi().patch)(
|
||||
TagUpdateDeleteApi(),
|
||||
TagUpdateRequestPayload(name="Updated"),
|
||||
request_context,
|
||||
"tag-1",
|
||||
)
|
||||
unwrap(TagUpdateDeleteApi().patch)(
|
||||
TagUpdateDeleteApi(),
|
||||
TagUpdateRequestPayload(name="Updated"),
|
||||
request_context,
|
||||
"tag-1",
|
||||
)
|
||||
|
||||
assert exc_info.value.__cause__ is None
|
||||
assert exc_info.value.__suppress_context__ is True
|
||||
@@ -316,9 +315,9 @@ class TestTagUpdateDeleteApi:
|
||||
app.test_request_context("/"),
|
||||
config_overrides_context(RBAC_ENABLED=False),
|
||||
patch.object(module, "current_account_with_tenant", return_value=(dataset_operator, "tenant-1")),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
unwrap(TagUpdateDeleteApi().delete)(TagUpdateDeleteApi(), request_context, "tag-1")
|
||||
unwrap(TagUpdateDeleteApi().delete)(TagUpdateDeleteApi(), request_context, "tag-1")
|
||||
|
||||
def test_delete_calls_application_service(
|
||||
self, app: Flask, request_context: RequestContext, tags_service: MagicMock
|
||||
@@ -364,9 +363,9 @@ class TestTagUpdateDeleteApi:
|
||||
config_overrides_context(RBAC_ENABLED=True),
|
||||
patch.object(module, "current_account_with_tenant", return_value=(owner, "tenant-1")),
|
||||
patch.object(module, "enforce_rbac_access") as enforce_rbac_access,
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
unwrap(TagUpdateDeleteApi().delete)(TagUpdateDeleteApi(), request_context, "tag-1")
|
||||
unwrap(TagUpdateDeleteApi().delete)(TagUpdateDeleteApi(), request_context, "tag-1")
|
||||
|
||||
enforce_rbac_access.assert_not_called()
|
||||
|
||||
@@ -425,9 +424,9 @@ class TestTagBindings:
|
||||
app.test_request_context("/"),
|
||||
config_overrides_context(RBAC_ENABLED=False),
|
||||
patch.object(module, "current_account_with_tenant", return_value=(owner, "tenant-1")),
|
||||
pytest.raises(NotFound, match="App not found") as exc_info,
|
||||
):
|
||||
with pytest.raises(NotFound, match="App not found") as exc_info:
|
||||
unwrap(TagBindingCollectionApi().post)(TagBindingCollectionApi(), payload, request_context)
|
||||
unwrap(TagBindingCollectionApi().post)(TagBindingCollectionApi(), payload, request_context)
|
||||
|
||||
assert exc_info.value.__cause__ is None
|
||||
assert exc_info.value.__suppress_context__ is True
|
||||
@@ -440,9 +439,9 @@ class TestTagBindings:
|
||||
app.test_request_context("/"),
|
||||
config_overrides_context(RBAC_ENABLED=False),
|
||||
patch.object(module, "current_account_with_tenant", return_value=(readonly, "tenant-1")),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
unwrap(TagBindingCollectionApi().post)(TagBindingCollectionApi(), payload, request_context)
|
||||
unwrap(TagBindingCollectionApi().post)(TagBindingCollectionApi(), payload, request_context)
|
||||
|
||||
def test_remove_passes_stable_binding_input(
|
||||
self, app: Flask, request_context: RequestContext, tags_service: MagicMock
|
||||
@@ -478,9 +477,9 @@ class TestTagBindings:
|
||||
app.test_request_context("/"),
|
||||
config_overrides_context(RBAC_ENABLED=False),
|
||||
patch.object(module, "current_account_with_tenant", return_value=(owner, "tenant-1")),
|
||||
pytest.raises(NotFound, match="Dataset not found") as exc_info,
|
||||
):
|
||||
with pytest.raises(NotFound, match="Dataset not found") as exc_info:
|
||||
unwrap(TagBindingRemoveApi().post)(TagBindingRemoveApi(), payload, request_context)
|
||||
unwrap(TagBindingRemoveApi().post)(TagBindingRemoveApi(), payload, request_context)
|
||||
|
||||
assert exc_info.value.__cause__ is None
|
||||
assert exc_info.value.__suppress_context__ is True
|
||||
@@ -493,9 +492,9 @@ class TestTagBindings:
|
||||
app.test_request_context("/"),
|
||||
config_overrides_context(RBAC_ENABLED=False),
|
||||
patch.object(module, "current_account_with_tenant", return_value=(readonly, "tenant-1")),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
unwrap(TagBindingRemoveApi().post)(TagBindingRemoveApi(), payload, request_context)
|
||||
unwrap(TagBindingRemoveApi().post)(TagBindingRemoveApi(), payload, request_context)
|
||||
|
||||
|
||||
class TestTagResponseAndRoutes:
|
||||
|
||||
@@ -162,12 +162,14 @@ def test_create_agent_api_key_requires_published_access(sqlite_session: Session)
|
||||
session = sqlite_session
|
||||
app = _persist_app(session, mode=AppMode.AGENT)
|
||||
|
||||
with patch(
|
||||
"controllers.console.apikey.AppService.ensure_agent_app_access_ready",
|
||||
side_effect=AgentAccessNotReadyError(),
|
||||
) as ensure_access_ready:
|
||||
with pytest.raises(AgentAccessNotReadyError):
|
||||
resource._create_api_key("app-1", "tenant-1", session=session)
|
||||
with (
|
||||
patch(
|
||||
"controllers.console.apikey.AppService.ensure_agent_app_access_ready",
|
||||
side_effect=AgentAccessNotReadyError(),
|
||||
) as ensure_access_ready,
|
||||
pytest.raises(AgentAccessNotReadyError),
|
||||
):
|
||||
resource._create_api_key("app-1", "tenant-1", session=session)
|
||||
|
||||
ensure_access_ready.assert_called_once_with(app, session=session)
|
||||
assert session.scalar(select(ApiToken)) is None
|
||||
@@ -234,15 +236,14 @@ def test_delete_api_key_rejects_foreign_tenant_token(sqlite_session: Session) ->
|
||||
session.add(api_key)
|
||||
session.commit()
|
||||
|
||||
with patch("controllers.console.apikey.ApiTokenCache.delete") as delete_cache:
|
||||
with pytest.raises(NotFound):
|
||||
resource._delete_api_key(
|
||||
"app-1",
|
||||
"key-1",
|
||||
"tenant-1",
|
||||
_make_account(TenantAccountRole.OWNER),
|
||||
session=session,
|
||||
)
|
||||
with patch("controllers.console.apikey.ApiTokenCache.delete") as delete_cache, pytest.raises(NotFound):
|
||||
resource._delete_api_key(
|
||||
"app-1",
|
||||
"key-1",
|
||||
"tenant-1",
|
||||
_make_account(TenantAccountRole.OWNER),
|
||||
session=session,
|
||||
)
|
||||
|
||||
delete_cache.assert_not_called()
|
||||
assert session.get(ApiToken, "key-1") is api_key
|
||||
@@ -279,12 +280,14 @@ def test_api_key_lists_require_matching_rbac_permission(config_overrides: Callab
|
||||
patch.object(BaseApiKeyListResource, "_get_api_key_list") as get_api_key_list,
|
||||
):
|
||||
for invoke, expected_gates in cases:
|
||||
with patch(
|
||||
"controllers.common.wraps.enforce_rbac_access",
|
||||
side_effect=[None] * (len(expected_gates) - 1) + [Forbidden()],
|
||||
) as enforce_rbac_access:
|
||||
with pytest.raises(Forbidden):
|
||||
invoke()
|
||||
with (
|
||||
patch(
|
||||
"controllers.common.wraps.enforce_rbac_access",
|
||||
side_effect=[None] * (len(expected_gates) - 1) + [Forbidden()],
|
||||
) as enforce_rbac_access,
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
invoke()
|
||||
|
||||
assert [
|
||||
(kwargs["resource_type"], kwargs["scene"], kwargs["resource_required"])
|
||||
|
||||
@@ -212,13 +212,15 @@ def test_console_setup_translates_service_errors_to_controller_errors(
|
||||
"language": "en-US",
|
||||
}
|
||||
)
|
||||
with app.test_request_context(
|
||||
"/console/api/setup",
|
||||
method="POST",
|
||||
headers={"CF-Connecting-IP": "203.0.113.7"},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/setup",
|
||||
method="POST",
|
||||
headers={"CF-Connecting-IP": "203.0.113.7"},
|
||||
),
|
||||
pytest.raises(expected_controller_error) as raised,
|
||||
):
|
||||
with pytest.raises(expected_controller_error) as raised:
|
||||
setup_controller.setup_system(payload)
|
||||
setup_controller.setup_system(payload)
|
||||
|
||||
assert type(raised.value) is expected_controller_error
|
||||
mark_setup_completed.assert_not_called()
|
||||
|
||||
@@ -132,9 +132,8 @@ class TestFileApiPost:
|
||||
api = FileApi()
|
||||
post_method = unwrap(api.post)
|
||||
|
||||
with app.test_request_context(method="POST", data={}):
|
||||
with pytest.raises(NoFileUploadedError):
|
||||
post_method(api, mock_account_context)
|
||||
with app.test_request_context(method="POST", data={}), pytest.raises(NoFileUploadedError):
|
||||
post_method(api, mock_account_context)
|
||||
|
||||
def test_too_many_files(self, app: Flask, mock_account_context):
|
||||
api = FileApi()
|
||||
@@ -161,9 +160,8 @@ class TestFileApiPost:
|
||||
"file": (io.BytesIO(b"abc"), ""),
|
||||
}
|
||||
|
||||
with app.test_request_context(method="POST", data=data):
|
||||
with pytest.raises(FilenameNotExistsError):
|
||||
post_method(api, mock_account_context)
|
||||
with app.test_request_context(method="POST", data=data), pytest.raises(FilenameNotExistsError):
|
||||
post_method(api, mock_account_context)
|
||||
|
||||
def test_dataset_upload_without_permission(self, app: Flask, mock_current_user):
|
||||
mock_current_user.role = TenantAccountRole.NORMAL
|
||||
@@ -176,9 +174,8 @@ class TestFileApiPost:
|
||||
"source": "datasets",
|
||||
}
|
||||
|
||||
with app.test_request_context(method="POST", data=data):
|
||||
with pytest.raises(Forbidden):
|
||||
post_method(api, mock_current_user)
|
||||
with app.test_request_context(method="POST", data=data), pytest.raises(Forbidden):
|
||||
post_method(api, mock_current_user)
|
||||
|
||||
def test_successful_upload(self, app: Flask, mock_account_context, mock_file_service):
|
||||
api = FileApi()
|
||||
@@ -275,9 +272,8 @@ class TestFileApiPost:
|
||||
"file": (io.BytesIO(b"x" * 1000000), "big.txt"),
|
||||
}
|
||||
|
||||
with app.test_request_context(method="POST", data=data):
|
||||
with pytest.raises(FileTooLargeError):
|
||||
post_method(api, mock_account_context)
|
||||
with app.test_request_context(method="POST", data=data), pytest.raises(FileTooLargeError):
|
||||
post_method(api, mock_account_context)
|
||||
|
||||
def test_unsupported_file_type(self, app: Flask, mock_account_context, mock_file_service):
|
||||
api = FileApi()
|
||||
@@ -292,9 +288,8 @@ class TestFileApiPost:
|
||||
"file": (io.BytesIO(b"x"), "bad.exe"),
|
||||
}
|
||||
|
||||
with app.test_request_context(method="POST", data=data):
|
||||
with pytest.raises(UnsupportedFileTypeError):
|
||||
post_method(api, mock_account_context)
|
||||
with app.test_request_context(method="POST", data=data), pytest.raises(UnsupportedFileTypeError):
|
||||
post_method(api, mock_account_context)
|
||||
|
||||
def test_blocked_extension(self, app: Flask, mock_account_context, mock_file_service):
|
||||
api = FileApi()
|
||||
@@ -309,9 +304,8 @@ class TestFileApiPost:
|
||||
"file": (io.BytesIO(b"x"), "blocked.txt"),
|
||||
}
|
||||
|
||||
with app.test_request_context(method="POST", data=data):
|
||||
with pytest.raises(BlockedFileExtensionError):
|
||||
post_method(api, mock_account_context)
|
||||
with app.test_request_context(method="POST", data=data), pytest.raises(BlockedFileExtensionError):
|
||||
post_method(api, mock_account_context)
|
||||
|
||||
|
||||
class TestFilePreviewApi:
|
||||
|
||||
@@ -136,9 +136,8 @@ def test_get_form_definition_not_found(app: Flask, monkeypatch: pytest.MonkeyPat
|
||||
api = ConsoleHumanInputFormApi()
|
||||
handler = unwrap(api.get)
|
||||
|
||||
with app.test_request_context("/console/api/form/human_input/token", method="GET"):
|
||||
with pytest.raises(NotFoundError):
|
||||
handler(api, "tenant-1", form_token="token")
|
||||
with app.test_request_context("/console/api/form/human_input/token", method="GET"), pytest.raises(NotFoundError):
|
||||
handler(api, "tenant-1", form_token="token")
|
||||
|
||||
|
||||
def test_post_form_invalid_recipient_type(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -156,19 +155,21 @@ def test_post_form_invalid_recipient_type(app: Flask, monkeypatch: pytest.Monkey
|
||||
api = ConsoleHumanInputFormApi()
|
||||
handler = unwrap(api.post)
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/form/human_input/token",
|
||||
method="POST",
|
||||
json={"inputs": {"content": "ok"}, "action": "approve"},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/form/human_input/token",
|
||||
method="POST",
|
||||
json={"inputs": {"content": "ok"}, "action": "approve"},
|
||||
),
|
||||
pytest.raises(NotFoundError),
|
||||
):
|
||||
with pytest.raises(NotFoundError):
|
||||
handler(
|
||||
api,
|
||||
HumanInputFormSubmitPayload.model_validate({"inputs": {"content": "ok"}, "action": "approve"}),
|
||||
"tenant-1",
|
||||
_account(),
|
||||
form_token="token",
|
||||
)
|
||||
handler(
|
||||
api,
|
||||
HumanInputFormSubmitPayload.model_validate({"inputs": {"content": "ok"}, "action": "approve"}),
|
||||
"tenant-1",
|
||||
_account(),
|
||||
form_token="token",
|
||||
)
|
||||
|
||||
|
||||
def test_post_form_rejects_webapp_recipient_type(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -186,19 +187,21 @@ def test_post_form_rejects_webapp_recipient_type(app: Flask, monkeypatch: pytest
|
||||
api = ConsoleHumanInputFormApi()
|
||||
handler = unwrap(api.post)
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/form/human_input/token",
|
||||
method="POST",
|
||||
json={"inputs": {"content": "ok"}, "action": "approve"},
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/form/human_input/token",
|
||||
method="POST",
|
||||
json={"inputs": {"content": "ok"}, "action": "approve"},
|
||||
),
|
||||
pytest.raises(NotFoundError),
|
||||
):
|
||||
with pytest.raises(NotFoundError):
|
||||
handler(
|
||||
api,
|
||||
HumanInputFormSubmitPayload.model_validate({"inputs": {"content": "ok"}, "action": "approve"}),
|
||||
"tenant-1",
|
||||
_account(),
|
||||
form_token="token",
|
||||
)
|
||||
handler(
|
||||
api,
|
||||
HumanInputFormSubmitPayload.model_validate({"inputs": {"content": "ok"}, "action": "approve"}),
|
||||
"tenant-1",
|
||||
_account(),
|
||||
form_token="token",
|
||||
)
|
||||
|
||||
|
||||
def test_post_form_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -290,9 +293,8 @@ def test_workflow_events_not_found(app: Flask, monkeypatch: pytest.MonkeyPatch)
|
||||
api = ConsoleWorkflowEventsApi()
|
||||
handler = unwrap(api.get)
|
||||
|
||||
with app.test_request_context("/console/api/workflow/run/events", method="GET"):
|
||||
with pytest.raises(NotFoundError):
|
||||
handler(api, "t1", _account(account_id="u1"), workflow_run_id="run-1")
|
||||
with app.test_request_context("/console/api/workflow/run/events", method="GET"), pytest.raises(NotFoundError):
|
||||
handler(api, "t1", _account(account_id="u1"), workflow_run_id="run-1")
|
||||
|
||||
|
||||
def test_workflow_events_requires_account(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -311,9 +313,8 @@ def test_workflow_events_requires_account(app: Flask, monkeypatch: pytest.Monkey
|
||||
api = ConsoleWorkflowEventsApi()
|
||||
handler = unwrap(api.get)
|
||||
|
||||
with app.test_request_context("/console/api/workflow/run/events", method="GET"):
|
||||
with pytest.raises(NotFoundError):
|
||||
handler(api, "t1", _account(account_id="u1"), workflow_run_id="run-1")
|
||||
with app.test_request_context("/console/api/workflow/run/events", method="GET"), pytest.raises(NotFoundError):
|
||||
handler(api, "t1", _account(account_id="u1"), workflow_run_id="run-1")
|
||||
|
||||
|
||||
def test_workflow_events_requires_creator(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -332,9 +333,8 @@ def test_workflow_events_requires_creator(app: Flask, monkeypatch: pytest.Monkey
|
||||
api = ConsoleWorkflowEventsApi()
|
||||
handler = unwrap(api.get)
|
||||
|
||||
with app.test_request_context("/console/api/workflow/run/events", method="GET"):
|
||||
with pytest.raises(NotFoundError):
|
||||
handler(api, "t1", _account(account_id="u1"), workflow_run_id="run-1")
|
||||
with app.test_request_context("/console/api/workflow/run/events", method="GET"), pytest.raises(NotFoundError):
|
||||
handler(api, "t1", _account(account_id="u1"), workflow_run_id="run-1")
|
||||
|
||||
|
||||
def test_workflow_events_finished(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
||||
@@ -54,9 +54,8 @@ def test_validate_init_password_already_setup(
|
||||
init_validation.validate_password.side_effect = AlreadyInitializedError
|
||||
app.secret_key = "test-secret"
|
||||
|
||||
with app.test_request_context("/console/api/init", method="POST"):
|
||||
with pytest.raises(AlreadySetupError):
|
||||
init_validate.validate_init_password(init_validate.InitValidatePayload(password="pw"))
|
||||
with app.test_request_context("/console/api/init", method="POST"), pytest.raises(AlreadySetupError):
|
||||
init_validate.validate_init_password(init_validate.InitValidatePayload(password="pw"))
|
||||
|
||||
|
||||
def test_validate_init_password_wrong_password(
|
||||
|
||||
@@ -190,9 +190,8 @@ def test_proxy_options_hides_unregistered_operations(
|
||||
def test_proxy_is_hidden_when_knowledge_fs_is_disabled(app: Flask, config_overrides: Callable[..., None]) -> None:
|
||||
config_overrides(KNOWLEDGE_FS_ENABLED=False)
|
||||
|
||||
with app.test_request_context("/console/api/knowledge-fs/knowledge-spaces", method="GET"):
|
||||
with pytest.raises(NotFound):
|
||||
_proxy_request("GET", "knowledge-spaces")
|
||||
with app.test_request_context("/console/api/knowledge-fs/knowledge-spaces", method="GET"), pytest.raises(NotFound):
|
||||
_proxy_request("GET", "knowledge-spaces")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -233,12 +232,14 @@ def test_generic_get_hides_unregistered_methods(
|
||||
monkeypatch.setattr("controllers.console.knowledge_fs_proxy._proxy_request", proxy)
|
||||
route = unwrap(proxy_knowledge_fs_get)
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/knowledge-fs/knowledge-spaces",
|
||||
method=method,
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/knowledge-fs/knowledge-spaces",
|
||||
method=method,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
route("knowledge-spaces")
|
||||
route("knowledge-spaces")
|
||||
|
||||
proxy.assert_not_called()
|
||||
|
||||
@@ -356,14 +357,16 @@ def test_denied_write_does_not_consume_the_workspace_rate_limit(
|
||||
monkeypatch.setattr("services.knowledge_fs_proxy.ssrf_proxy.make_request", upstream_request)
|
||||
route = unwrap(proxy_knowledge_fs_write)
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/knowledge-fs/knowledge-spaces",
|
||||
method="POST",
|
||||
data=b"{}",
|
||||
content_type="application/json",
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/knowledge-fs/knowledge-spaces",
|
||||
method="POST",
|
||||
data=b"{}",
|
||||
content_type="application/json",
|
||||
),
|
||||
pytest.raises(Forbidden),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
route("knowledge-spaces")
|
||||
route("knowledge-spaces")
|
||||
|
||||
zadd.assert_not_called()
|
||||
upstream_request.assert_not_called()
|
||||
@@ -618,9 +621,8 @@ def test_authorized_service_denial_is_exposed_as_forbidden(
|
||||
_set_current_workspace(monkeypatch)
|
||||
route = unwrap(proxy_knowledge_fs_get)
|
||||
|
||||
with app.test_request_context("/console/api/knowledge-fs/knowledge-spaces"):
|
||||
with pytest.raises(Forbidden):
|
||||
route("knowledge-spaces")
|
||||
with app.test_request_context("/console/api/knowledge-fs/knowledge-spaces"), pytest.raises(Forbidden):
|
||||
route("knowledge-spaces")
|
||||
|
||||
|
||||
def test_generic_post_rejects_oversized_body(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -635,14 +637,16 @@ def test_generic_post_rejects_oversized_body(app: Flask, monkeypatch: pytest.Mon
|
||||
|
||||
monkeypatch.setattr("controllers.console.knowledge_fs_proxy._MAX_PROXY_BODY_BYTES", 8)
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/knowledge-fs/knowledge-spaces",
|
||||
method="POST",
|
||||
data=b"x" * 9,
|
||||
content_type="application/json",
|
||||
with (
|
||||
app.test_request_context(
|
||||
"/console/api/knowledge-fs/knowledge-spaces",
|
||||
method="POST",
|
||||
data=b"x" * 9,
|
||||
content_type="application/json",
|
||||
),
|
||||
pytest.raises(RequestEntityTooLarge),
|
||||
):
|
||||
with pytest.raises(RequestEntityTooLarge):
|
||||
route("knowledge-spaces")
|
||||
route("knowledge-spaces")
|
||||
|
||||
forward.assert_not_called()
|
||||
|
||||
@@ -666,9 +670,11 @@ def test_server_credential_rejection_is_not_exposed_as_browser_auth_failure(
|
||||
_set_current_workspace(monkeypatch)
|
||||
route = unwrap(proxy_knowledge_fs_get)
|
||||
|
||||
with app.test_request_context("/console/api/knowledge-fs/knowledge-spaces"):
|
||||
with pytest.raises(BadGateway, match="authentication failed"):
|
||||
route("knowledge-spaces")
|
||||
with (
|
||||
app.test_request_context("/console/api/knowledge-fs/knowledge-spaces"),
|
||||
pytest.raises(BadGateway, match="authentication failed"),
|
||||
):
|
||||
route("knowledge-spaces")
|
||||
|
||||
|
||||
def test_resource_authorization_rejection_is_exposed_as_forbidden(
|
||||
@@ -690,9 +696,8 @@ def test_resource_authorization_rejection_is_exposed_as_forbidden(
|
||||
_set_current_workspace(monkeypatch)
|
||||
route = unwrap(proxy_knowledge_fs_get)
|
||||
|
||||
with app.test_request_context("/console/api/knowledge-fs/knowledge-spaces"):
|
||||
with pytest.raises(Forbidden):
|
||||
route("knowledge-spaces")
|
||||
with app.test_request_context("/console/api/knowledge-fs/knowledge-spaces"), pytest.raises(Forbidden):
|
||||
route("knowledge-spaces")
|
||||
|
||||
|
||||
def test_proxy_response_applies_operation_specific_error_status_mapping() -> None:
|
||||
@@ -762,9 +767,11 @@ def test_configuration_error_is_reported_as_unavailable(app: Flask, monkeypatch:
|
||||
_set_current_workspace(monkeypatch)
|
||||
route = unwrap(proxy_knowledge_fs_get)
|
||||
|
||||
with app.test_request_context("/console/api/knowledge-fs/knowledge-spaces"):
|
||||
with pytest.raises(ServiceUnavailable, match="misconfigured"):
|
||||
route("knowledge-spaces")
|
||||
with (
|
||||
app.test_request_context("/console/api/knowledge-fs/knowledge-spaces"),
|
||||
pytest.raises(ServiceUnavailable, match="misconfigured"),
|
||||
):
|
||||
route("knowledge-spaces")
|
||||
|
||||
|
||||
def test_disallowed_kfs_route_is_hidden_as_not_found(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -775,9 +782,8 @@ def test_disallowed_kfs_route_is_hidden_as_not_found(app: Flask, monkeypatch: py
|
||||
_set_current_workspace(monkeypatch)
|
||||
route = unwrap(proxy_knowledge_fs_get)
|
||||
|
||||
with app.test_request_context("/console/api/knowledge-fs/openapi.json"):
|
||||
with pytest.raises(NotFound):
|
||||
route("openapi.json")
|
||||
with app.test_request_context("/console/api/knowledge-fs/openapi.json"), pytest.raises(NotFound):
|
||||
route("openapi.json")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("method", ["PATCH", "POST"])
|
||||
@@ -789,9 +795,8 @@ def test_disallowed_non_get_route_is_hidden_as_not_found(
|
||||
_set_current_workspace(monkeypatch)
|
||||
route = unwrap(proxy_knowledge_fs_write)
|
||||
|
||||
with app.test_request_context("/console/api/knowledge-fs/not-a-route", method=method):
|
||||
with pytest.raises(NotFound):
|
||||
route("not-a-route")
|
||||
with app.test_request_context("/console/api/knowledge-fs/not-a-route", method=method), pytest.raises(NotFound):
|
||||
route("not-a-route")
|
||||
|
||||
|
||||
def test_raw_route_uses_console_json_error_handler(app: Flask) -> None:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user