Polish community UI copy and cleanup
This commit is contained in:
@@ -4,18 +4,15 @@ mod auth;
|
||||
mod observability;
|
||||
mod operation;
|
||||
mod secret;
|
||||
mod stream;
|
||||
mod upstream;
|
||||
mod workspace;
|
||||
mod yaml_import;
|
||||
|
||||
use crank_core::{
|
||||
AgentId, AgentOperationBinding, AgentStatus, AgentVersion, AsyncJobHandle, AsyncJobId,
|
||||
AuthProfile, GraphqlOperationType, HttpMethod, InvitationId, InvitationToken, InvocationLog,
|
||||
InvocationLogId, JobStatus, MembershipRole, OperationId, OperationStatus, PlatformApiKey,
|
||||
PlatformApiKeyId, PlatformApiKeyStatus, Secret, SecretId, SecretVersion, StreamSession,
|
||||
StreamSessionId, StreamStatus, Target, UsageRollup, User, UserId, UserSessionId, Workspace,
|
||||
WorkspaceId,
|
||||
AgentId, AgentOperationBinding, AgentStatus, AgentVersion, AuthProfile, HttpMethod,
|
||||
InvitationId, InvitationToken, InvocationLog, InvocationLogId, MembershipRole, OperationId,
|
||||
OperationStatus, PlatformApiKey, PlatformApiKeyId, PlatformApiKeyStatus, Secret, SecretId,
|
||||
SecretVersion, Target, UsageRollup, User, UserId, UserSessionId, Workspace, WorkspaceId,
|
||||
};
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use serde_json::Value;
|
||||
@@ -32,18 +29,16 @@ use crate::{
|
||||
error::RegistryError,
|
||||
migrations,
|
||||
model::{
|
||||
AgentSummary, AgentVersionRecord, AsyncJobFilter, AuthUserRecord,
|
||||
CreateAgentDraftVersionRequest, CreateAgentRequest, CreateAsyncJobRequest,
|
||||
CreateInvitationRequest, CreateInvocationLogRequest, CreatePlatformApiKeyRequest,
|
||||
CreateSecretRequest, CreateStreamSessionRequest, CreateVersionRequest,
|
||||
AgentSummary, AgentVersionRecord, AuthUserRecord, CreateAgentDraftVersionRequest,
|
||||
CreateAgentRequest, CreateInvitationRequest, CreateInvocationLogRequest,
|
||||
CreatePlatformApiKeyRequest, CreateSecretRequest, CreateVersionRequest,
|
||||
CreateWorkspaceRequest, CreateYamlImportJobRequest, DescriptorMetadata, InvitationRecord,
|
||||
InvocationLogRecord, ListInvocationLogsQuery, MembershipRecord, OperationAgentRef,
|
||||
OperationSampleMetadata, OperationSummary, OperationUsageSummary, OperationVersionRecord,
|
||||
Page, PlatformApiKeyRecord, PublishAgentRequest, PublishRequest, PublishedAgentTool,
|
||||
PlatformApiKeyRecord, PublishAgentRequest, PublishRequest, PublishedAgentTool,
|
||||
RegistryOperation, RotateSecretRequest, SaveAgentBindingsRequest, SaveAuthProfileRequest,
|
||||
SaveDescriptorMetadataRequest, SaveSampleMetadataRequest, SaveWorkspaceUpstreamRequest,
|
||||
SecretRecord, SecretVersionRecord, SessionRecord, StreamSessionFilter,
|
||||
UpdateAsyncJobStatusRequest, UpdateStreamSessionStateRequest, UpdateWorkspaceRequest,
|
||||
SecretRecord, SecretVersionRecord, SessionRecord, UpdateWorkspaceRequest,
|
||||
UsageAgentBreakdown, UsageOperationBreakdown, UsageQuery, UsageRollupRecord, UsageSummary,
|
||||
UsageTimelinePoint, WorkspaceMembershipRecord, WorkspaceRecord, WorkspaceUpstream,
|
||||
YamlImportJob, YamlImportJobCompletion, YamlImportJobId, YamlImportJobStatus,
|
||||
@@ -520,52 +515,6 @@ fn map_user_update_error(error: sqlx::Error, user_id: &UserId, email: &str) -> R
|
||||
}
|
||||
}
|
||||
|
||||
fn map_stream_session(row: &PgRow) -> Result<StreamSession, RegistryError> {
|
||||
Ok(StreamSession {
|
||||
id: StreamSessionId::new(row.try_get::<String, _>("id")?),
|
||||
workspace_id: WorkspaceId::new(row.try_get::<String, _>("workspace_id")?),
|
||||
agent_id: row
|
||||
.try_get::<Option<String>, _>("agent_id")?
|
||||
.map(AgentId::new),
|
||||
operation_id: OperationId::new(row.try_get::<String, _>("operation_id")?),
|
||||
protocol: deserialize_enum_text(&row.try_get::<String, _>("protocol")?, "protocol")?,
|
||||
mode: deserialize_enum_text(&row.try_get::<String, _>("mode")?, "mode")?,
|
||||
status: deserialize_enum_text(&row.try_get::<String, _>("status")?, "status")?,
|
||||
cursor: row
|
||||
.try_get::<Option<Json<Value>>, _>("cursor_json")?
|
||||
.map(|value| value.0),
|
||||
state: row.try_get::<Json<Value>, _>("state_json")?.0,
|
||||
expires_at: row.try_get("expires_at")?,
|
||||
last_poll_at: row.try_get("last_poll_at")?,
|
||||
created_at: row.try_get("created_at")?,
|
||||
closed_at: row.try_get("closed_at")?,
|
||||
})
|
||||
}
|
||||
|
||||
fn map_async_job(row: &PgRow) -> Result<AsyncJobHandle, RegistryError> {
|
||||
Ok(AsyncJobHandle {
|
||||
id: AsyncJobId::new(row.try_get::<String, _>("id")?),
|
||||
workspace_id: WorkspaceId::new(row.try_get::<String, _>("workspace_id")?),
|
||||
agent_id: row
|
||||
.try_get::<Option<String>, _>("agent_id")?
|
||||
.map(AgentId::new),
|
||||
operation_id: OperationId::new(row.try_get::<String, _>("operation_id")?),
|
||||
status: deserialize_enum_text(&row.try_get::<String, _>("status")?, "status")?,
|
||||
progress: row.try_get::<Json<Value>, _>("progress_json")?.0,
|
||||
result: row
|
||||
.try_get::<Option<Json<Value>>, _>("result_json")?
|
||||
.map(|value| value.0),
|
||||
error: row
|
||||
.try_get::<Option<Json<Value>>, _>("error_json")?
|
||||
.map(|value| value.0),
|
||||
expires_at: row.try_get("expires_at")?,
|
||||
last_poll_at: row.try_get("last_poll_at")?,
|
||||
created_at: row.try_get("created_at")?,
|
||||
updated_at: row.try_get("updated_at")?,
|
||||
finished_at: row.try_get("finished_at")?,
|
||||
})
|
||||
}
|
||||
|
||||
fn map_invocation_log_record(row: &PgRow) -> Result<InvocationLogRecord, RegistryError> {
|
||||
Ok(InvocationLogRecord {
|
||||
log: InvocationLog {
|
||||
@@ -627,22 +576,6 @@ fn target_summary(target: &Target) -> (String, String) {
|
||||
}
|
||||
.to_owned(),
|
||||
),
|
||||
Target::Graphql(graphql) => (
|
||||
graphql.endpoint.clone(),
|
||||
match graphql.operation_type {
|
||||
GraphqlOperationType::Query => "QUERY",
|
||||
GraphqlOperationType::Mutation => "MUTATION",
|
||||
}
|
||||
.to_owned(),
|
||||
),
|
||||
Target::Grpc(grpc) => (grpc.server_addr.clone(), grpc.method.clone()),
|
||||
Target::Websocket(websocket) => (websocket.url.clone(), "SUBSCRIBE".to_owned()),
|
||||
Target::Soap(soap) => (
|
||||
soap.endpoint_override
|
||||
.clone()
|
||||
.unwrap_or_else(|| format!("wsdl://{}", soap.service_name)),
|
||||
soap.operation_name.clone(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1061,59 +994,6 @@ fn from_db_version(value: i32, field: &'static str) -> Result<u32, RegistryError
|
||||
})
|
||||
}
|
||||
|
||||
fn validate_stream_session_transition(
|
||||
session_id: &StreamSessionId,
|
||||
from: StreamStatus,
|
||||
to: StreamStatus,
|
||||
) -> Result<(), RegistryError> {
|
||||
let allowed = matches!(
|
||||
(from, to),
|
||||
(StreamStatus::Created, StreamStatus::Running)
|
||||
| (StreamStatus::Running, StreamStatus::Running)
|
||||
| (StreamStatus::Running, StreamStatus::Stopped)
|
||||
| (StreamStatus::Running, StreamStatus::Expired)
|
||||
| (StreamStatus::Running, StreamStatus::Failed)
|
||||
);
|
||||
|
||||
if allowed {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RegistryError::InvalidStreamSessionTransition {
|
||||
session_id: session_id.as_str().to_owned(),
|
||||
from: serialize_enum_text(&from, "status").unwrap_or_else(|_| "unknown".to_owned()),
|
||||
to: serialize_enum_text(&to, "status").unwrap_or_else(|_| "unknown".to_owned()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_async_job_transition(
|
||||
job_id: &AsyncJobId,
|
||||
from: JobStatus,
|
||||
to: JobStatus,
|
||||
) -> Result<(), RegistryError> {
|
||||
let allowed = matches!(
|
||||
(from, to),
|
||||
(JobStatus::Created, JobStatus::Running)
|
||||
| (JobStatus::Running, JobStatus::Running)
|
||||
| (JobStatus::Running, JobStatus::Completed)
|
||||
| (JobStatus::Running, JobStatus::Failed)
|
||||
| (JobStatus::Running, JobStatus::Cancelled)
|
||||
| (JobStatus::Completed, JobStatus::Expired)
|
||||
| (JobStatus::Failed, JobStatus::Expired)
|
||||
| (JobStatus::Cancelled, JobStatus::Expired)
|
||||
);
|
||||
|
||||
if allowed {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RegistryError::InvalidAsyncJobTransition {
|
||||
job_id: job_id.as_str().to_owned(),
|
||||
from: serialize_enum_text(&from, "status").unwrap_or_else(|_| "unknown".to_owned()),
|
||||
to: serialize_enum_text(&to, "status").unwrap_or_else(|_| "unknown".to_owned()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn to_u64(value: i64, field: &'static str) -> Result<u64, RegistryError> {
|
||||
u64::try_from(value).map_err(|_| RegistryError::InvalidNumericValue { field, value })
|
||||
}
|
||||
@@ -1137,13 +1017,12 @@ mod tests {
|
||||
|
||||
use crank_core::{
|
||||
AgentId, AgentOperationBinding, AgentStatus, AgentVersion, ApiKeyHeaderAuthConfig,
|
||||
AsyncJobHandle, AuthConfig, AuthKind, AuthProfile, ConfigExport, ExecutionConfig,
|
||||
ExportMode, GeneratedDraft, GeneratedDraftStatus, HttpMethod, InvocationLog, JobStatus,
|
||||
MembershipRole, OperationId, OperationSecurityLevel, OperationStatus, PlatformApiKey,
|
||||
PlatformApiKeyId, PlatformApiKeyScope, PlatformApiKeyStatus, Protocol, RestTarget,
|
||||
RetryPolicy, Samples, SecretId, StreamSession, StreamSessionId, StreamStatus, Target,
|
||||
ToolDescription, ToolExample, User, UserId, UserSessionId, WizardState, Workspace,
|
||||
WorkspaceId,
|
||||
AuthConfig, AuthKind, AuthProfile, ConfigExport, ExecutionConfig, ExportMode,
|
||||
GeneratedDraft, GeneratedDraftStatus, HttpMethod, InvocationLog, MembershipRole,
|
||||
OperationId, OperationSecurityLevel, OperationStatus, PlatformApiKey, PlatformApiKeyId,
|
||||
PlatformApiKeyScope, PlatformApiKeyStatus, Protocol, RestTarget, RetryPolicy, Samples,
|
||||
SecretId, Target, ToolDescription, ToolExample, User, UserId, UserSessionId, WizardState,
|
||||
Workspace, WorkspaceId,
|
||||
};
|
||||
use crank_mapping::{MappingRule, MappingSet};
|
||||
use crank_schema::{Schema, SchemaKind};
|
||||
@@ -1154,14 +1033,12 @@ mod tests {
|
||||
use crate::{
|
||||
PostgresRegistry, RegistryError,
|
||||
model::{
|
||||
AsyncJobFilter, CreateAgentRequest, CreateAsyncJobRequest, CreateInvocationLogRequest,
|
||||
CreatePlatformApiKeyRequest, CreateStreamSessionRequest, CreateVersionRequest,
|
||||
CreateWorkspaceRequest, CreateYamlImportJobRequest, DescriptorKind, DescriptorMetadata,
|
||||
OperationSampleMetadata, PlatformApiKeyRecord, PublishAgentRequest, PublishRequest,
|
||||
RegistryOperation, SampleKind, SaveAuthProfileRequest, SaveDescriptorMetadataRequest,
|
||||
SaveSampleMetadataRequest, StreamSessionFilter, UpdateAsyncJobStatusRequest,
|
||||
UpdateStreamSessionStateRequest, WorkspaceRecord, YamlImportJobCompletion,
|
||||
YamlImportJobId, YamlImportJobStatus,
|
||||
CreateAgentRequest, CreateInvocationLogRequest, CreatePlatformApiKeyRequest,
|
||||
CreateVersionRequest, CreateWorkspaceRequest, CreateYamlImportJobRequest,
|
||||
DescriptorKind, DescriptorMetadata, OperationSampleMetadata, PlatformApiKeyRecord,
|
||||
PublishAgentRequest, PublishRequest, RegistryOperation, SampleKind,
|
||||
SaveAuthProfileRequest, SaveDescriptorMetadataRequest, SaveSampleMetadataRequest,
|
||||
WorkspaceRecord, YamlImportJobCompletion, YamlImportJobId, YamlImportJobStatus,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2032,284 +1909,6 @@ mod tests {
|
||||
|
||||
database.cleanup().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn manages_stream_sessions_with_transitions_and_cleanup() {
|
||||
let database = TestDatabase::new().await;
|
||||
let registry = database.registry().await;
|
||||
let operation = test_operation("op_stream_01", 1, OperationStatus::Draft);
|
||||
let session = test_stream_session("stream_01", "op_stream_01", StreamStatus::Running);
|
||||
|
||||
registry
|
||||
.create_operation(&test_workspace_id(), &operation, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
registry
|
||||
.create_stream_session(CreateStreamSessionRequest { session: &session })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let loaded = registry
|
||||
.get_stream_session(&session.id)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(loaded.status, StreamStatus::Running);
|
||||
|
||||
let closable_session =
|
||||
test_stream_session("stream_closable", "op_stream_01", StreamStatus::Running);
|
||||
registry
|
||||
.create_stream_session(CreateStreamSessionRequest {
|
||||
session: &closable_session,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
registry
|
||||
.close_stream_session(&closable_session.id, ×tamp("2026-04-06T12:00:30Z"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let closed = registry
|
||||
.get_stream_session(&closable_session.id)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(closed.status, StreamStatus::Stopped);
|
||||
|
||||
let touched = registry
|
||||
.touch_stream_session_poll(&session.id, ×tamp("2026-04-06T12:00:20Z"))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
touched.last_poll_at,
|
||||
Some(timestamp("2026-04-06T12:00:20Z"))
|
||||
);
|
||||
|
||||
let updated = registry
|
||||
.update_stream_session_state(UpdateStreamSessionStateRequest {
|
||||
session_id: &session.id,
|
||||
current_status: StreamStatus::Running,
|
||||
next_status: StreamStatus::Failed,
|
||||
cursor: Some(&json!({"cursor":"next"})),
|
||||
state: &json!({"phase":"failed"}),
|
||||
expires_at: Some(×tamp("2026-04-06T12:10:00Z")),
|
||||
last_poll_at: Some(×tamp("2026-04-06T12:01:00Z")),
|
||||
closed_at: Some(×tamp("2026-04-06T12:01:00Z")),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(updated.status, StreamStatus::Failed);
|
||||
assert_eq!(updated.closed_at, Some(timestamp("2026-04-06T12:01:00Z")));
|
||||
|
||||
let page = registry
|
||||
.list_stream_sessions(StreamSessionFilter {
|
||||
workspace_id: &test_workspace_id(),
|
||||
agent_id: None,
|
||||
operation_id: None,
|
||||
status: Some(StreamStatus::Failed),
|
||||
mode: Some(crank_core::ExecutionMode::Session),
|
||||
limit: 10,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(page.total, 1);
|
||||
assert_eq!(page.items[0].id, session.id);
|
||||
|
||||
let expired_session =
|
||||
test_stream_session("stream_expired", "op_stream_01", StreamStatus::Running);
|
||||
registry
|
||||
.create_stream_session(CreateStreamSessionRequest {
|
||||
session: &StreamSession {
|
||||
id: expired_session.id.clone(),
|
||||
expires_at: timestamp("2026-04-06T11:00:00Z"),
|
||||
..expired_session
|
||||
},
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let deleted = registry
|
||||
.delete_expired_stream_sessions(×tamp("2026-04-06T12:00:00Z"))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(deleted, 1);
|
||||
|
||||
database.cleanup().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rejects_invalid_stream_session_transition() {
|
||||
let database = TestDatabase::new().await;
|
||||
let registry = database.registry().await;
|
||||
let operation = test_operation("op_stream_02", 1, OperationStatus::Draft);
|
||||
let session = test_stream_session("stream_invalid", "op_stream_02", StreamStatus::Stopped);
|
||||
|
||||
registry
|
||||
.create_operation(&test_workspace_id(), &operation, None)
|
||||
.await
|
||||
.unwrap();
|
||||
registry
|
||||
.create_stream_session(CreateStreamSessionRequest { session: &session })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let error = registry
|
||||
.update_stream_session_state(UpdateStreamSessionStateRequest {
|
||||
session_id: &session.id,
|
||||
current_status: StreamStatus::Stopped,
|
||||
next_status: StreamStatus::Running,
|
||||
cursor: None,
|
||||
state: &json!({"phase":"resume"}),
|
||||
expires_at: None,
|
||||
last_poll_at: None,
|
||||
closed_at: None,
|
||||
})
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(
|
||||
error,
|
||||
RegistryError::InvalidStreamSessionTransition { .. }
|
||||
));
|
||||
|
||||
database.cleanup().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn manages_async_jobs_with_transitions_and_cleanup() {
|
||||
let database = TestDatabase::new().await;
|
||||
let registry = database.registry().await;
|
||||
let operation = test_operation("op_job_01", 1, OperationStatus::Draft);
|
||||
let job = test_async_job("job_01", "op_job_01", JobStatus::Running);
|
||||
|
||||
registry
|
||||
.create_operation(&test_workspace_id(), &operation, None)
|
||||
.await
|
||||
.unwrap();
|
||||
registry
|
||||
.create_async_job(CreateAsyncJobRequest { job: &job })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let cancellable_job = test_async_job("job_cancel", "op_job_01", JobStatus::Running);
|
||||
registry
|
||||
.create_async_job(CreateAsyncJobRequest {
|
||||
job: &cancellable_job,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
registry
|
||||
.cancel_async_job(&cancellable_job.id, ×tamp("2026-04-06T12:00:30Z"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let cancelled = registry
|
||||
.get_async_job(&cancellable_job.id)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(cancelled.status, JobStatus::Cancelled);
|
||||
|
||||
let updated = registry
|
||||
.update_async_job_status(UpdateAsyncJobStatusRequest {
|
||||
job_id: &job.id,
|
||||
current_status: JobStatus::Running,
|
||||
next_status: JobStatus::Completed,
|
||||
progress: &json!({"percent":100}),
|
||||
result: Some(&json!({"ok":true})),
|
||||
error: None,
|
||||
expires_at: Some(×tamp("2026-04-06T12:15:00Z")),
|
||||
updated_at: ×tamp("2026-04-06T12:01:00Z"),
|
||||
finished_at: Some(×tamp("2026-04-06T12:01:00Z")),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(updated.status, JobStatus::Completed);
|
||||
assert_eq!(updated.result, Some(json!({"ok":true})));
|
||||
|
||||
let loaded = registry.get_async_job(&job.id).await.unwrap().unwrap();
|
||||
assert_eq!(loaded.status, JobStatus::Completed);
|
||||
|
||||
let touched = registry
|
||||
.touch_async_job_poll(&job.id, ×tamp("2026-04-06T12:02:00Z"))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
touched.last_poll_at,
|
||||
Some(timestamp("2026-04-06T12:02:00Z"))
|
||||
);
|
||||
|
||||
let page = registry
|
||||
.list_async_jobs(AsyncJobFilter {
|
||||
workspace_id: &test_workspace_id(),
|
||||
agent_id: None,
|
||||
operation_id: None,
|
||||
status: Some(JobStatus::Completed),
|
||||
limit: 10,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(page.total, 1);
|
||||
|
||||
let expired_job = AsyncJobHandle {
|
||||
id: crank_core::AsyncJobId::new("job_expired"),
|
||||
expires_at: Some(timestamp("2026-04-06T11:00:00Z")),
|
||||
..test_async_job("job_expired", "op_job_01", JobStatus::Cancelled)
|
||||
};
|
||||
registry
|
||||
.create_async_job(CreateAsyncJobRequest { job: &expired_job })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let deleted = registry
|
||||
.delete_expired_async_jobs(×tamp("2026-04-06T12:00:00Z"))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(deleted, 1);
|
||||
|
||||
database.cleanup().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rejects_invalid_async_job_transition() {
|
||||
let database = TestDatabase::new().await;
|
||||
let registry = database.registry().await;
|
||||
let operation = test_operation("op_job_02", 1, OperationStatus::Draft);
|
||||
let job = test_async_job("job_invalid", "op_job_02", JobStatus::Completed);
|
||||
|
||||
registry
|
||||
.create_operation(&test_workspace_id(), &operation, None)
|
||||
.await
|
||||
.unwrap();
|
||||
registry
|
||||
.create_async_job(CreateAsyncJobRequest { job: &job })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let error = registry
|
||||
.update_async_job_status(UpdateAsyncJobStatusRequest {
|
||||
job_id: &job.id,
|
||||
current_status: JobStatus::Completed,
|
||||
next_status: JobStatus::Running,
|
||||
progress: &json!({"percent":50}),
|
||||
result: None,
|
||||
error: None,
|
||||
expires_at: None,
|
||||
updated_at: ×tamp("2026-04-06T12:01:00Z"),
|
||||
finished_at: None,
|
||||
})
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(
|
||||
error,
|
||||
RegistryError::InvalidAsyncJobTransition { .. }
|
||||
));
|
||||
|
||||
database.cleanup().await;
|
||||
}
|
||||
|
||||
struct TestDatabase {
|
||||
admin_pool: PgPool,
|
||||
database_url: String,
|
||||
@@ -2454,9 +2053,7 @@ mod tests {
|
||||
retry_policy: Some(RetryPolicy { max_attempts: 3 }),
|
||||
auth_profile_ref: Some("auth_crank".into()),
|
||||
headers: BTreeMap::from([("X-Request-Id".to_owned(), "static".to_owned())]),
|
||||
protocol_options: None,
|
||||
response_cache: None,
|
||||
streaming: None,
|
||||
},
|
||||
tool_description: ToolDescription {
|
||||
title: "Create lead".to_owned(),
|
||||
@@ -2469,8 +2066,6 @@ mod tests {
|
||||
samples: Some(Samples {
|
||||
input_json_sample_ref: Some("sample_input".into()),
|
||||
output_json_sample_ref: Some("sample_output".into()),
|
||||
proto_file_ref: None,
|
||||
descriptor_ref: None,
|
||||
}),
|
||||
generated_draft: Some(GeneratedDraft {
|
||||
status: GeneratedDraftStatus::Available,
|
||||
@@ -2496,25 +2091,6 @@ mod tests {
|
||||
published_at: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn test_stream_session(id: &str, operation_id: &str, status: StreamStatus) -> StreamSession {
|
||||
StreamSession {
|
||||
id: StreamSessionId::new(id),
|
||||
workspace_id: test_workspace_id(),
|
||||
agent_id: None,
|
||||
operation_id: OperationId::new(operation_id),
|
||||
protocol: Protocol::Rest,
|
||||
mode: crank_core::ExecutionMode::Session,
|
||||
status,
|
||||
cursor: Some(json!({"cursor":"initial"})),
|
||||
state: json!({"phase":"running"}),
|
||||
expires_at: timestamp("2026-04-06T12:05:00Z"),
|
||||
last_poll_at: None,
|
||||
created_at: timestamp("2026-04-06T12:00:00Z"),
|
||||
closed_at: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn test_agent(id: &str, status: AgentStatus) -> crank_core::Agent {
|
||||
crank_core::Agent {
|
||||
id: AgentId::new(id),
|
||||
@@ -2575,22 +2151,4 @@ mod tests {
|
||||
created_at: timestamp(created_at),
|
||||
}
|
||||
}
|
||||
|
||||
fn test_async_job(id: &str, operation_id: &str, status: JobStatus) -> AsyncJobHandle {
|
||||
AsyncJobHandle {
|
||||
id: crank_core::AsyncJobId::new(id),
|
||||
workspace_id: test_workspace_id(),
|
||||
agent_id: None,
|
||||
operation_id: OperationId::new(operation_id),
|
||||
status,
|
||||
progress: json!({"percent": 25}),
|
||||
result: None,
|
||||
error: None,
|
||||
expires_at: Some(timestamp("2026-04-06T12:05:00Z")),
|
||||
last_poll_at: None,
|
||||
created_at: timestamp("2026-04-06T12:00:00Z"),
|
||||
updated_at: timestamp("2026-04-06T12:00:00Z"),
|
||||
finished_at: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user