Compare commits

...

1 Commits

Author SHA1 Message Date
Asuka Minato
644dd8b281 sim117
.

.

.

.

.

.

?

?

?

done

fix

[autofix.ci] apply automated fixes

[autofix.ci] apply automated fixes

[autofix.ci] apply automated fixes (attempt 2/3)
2026-09-02 11:27:31 +09:00
235 changed files with 7202 additions and 6479 deletions

View File

@@ -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
]

View File

@@ -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()

View File

@@ -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:
"""

View File

@@ -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):

View File

@@ -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:

View File

@@ -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)

View File

@@ -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):

View File

@@ -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")

View File

@@ -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 ───────────────────────────────────────────────────────────

View File

@@ -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."""

View File

@@ -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):

View File

@@ -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()

View File

@@ -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

View File

@@ -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'",

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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:

View File

@@ -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)

View File

@@ -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()

View File

@@ -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()),
)

View File

@@ -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(),
)

View File

@@ -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))

View File

@@ -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:

View File

@@ -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:

View File

@@ -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")

View File

@@ -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

View File

@@ -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"

View File

@@ -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):
"""

View File

@@ -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()

View File

@@ -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()

View File

@@ -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,
)

View File

@@ -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(

View File

@@ -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

View File

@@ -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:

View File

@@ -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
)

View File

@@ -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")

View File

@@ -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

View File

@@ -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

View File

@@ -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):
"""

View File

@@ -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()

View File

@@ -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

View File

@@ -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():

View File

@@ -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

View File

@@ -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"}

View File

@@ -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 == []

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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:

View File

@@ -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(

View File

@@ -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({})

View File

@@ -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:

View File

@@ -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")

View File

@@ -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()

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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(

View File

@@ -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:

View File

@@ -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()

View File

@@ -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")

View File

@@ -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()

View File

@@ -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")

View File

@@ -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:

View File

@@ -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()

View File

@@ -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)

View File

@@ -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",

View File

@@ -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")

View File

@@ -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()

View File

@@ -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)

View File

@@ -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()

View File

@@ -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:

View File

@@ -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")

View File

@@ -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)

View File

@@ -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:

View File

@@ -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")

View File

@@ -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()

View File

@@ -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)

View File

@@ -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")

View File

@@ -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()

View File

@@ -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)

View File

@@ -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:

View File

@@ -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")

View File

@@ -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",

View File

@@ -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):

View File

@@ -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:

View File

@@ -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",

View File

@@ -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:

View File

@@ -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(

View File

@@ -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:

View File

@@ -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"])

View File

@@ -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()

View File

@@ -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:

View File

@@ -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:

View File

@@ -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(

View File

@@ -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