Add approval request HTTP surface
This commit is contained in:
@@ -5,13 +5,13 @@ use super::common::*;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crank_core::{
|
||||
AgentId, AgentOperationBinding, AgentStatus, AgentVersion, ApiKeyHeaderAuthConfig, AuthConfig,
|
||||
AuthKind, AuthProfile, ConfigExport, ExecutionConfig, ExportMode, GeneratedDraft,
|
||||
GeneratedDraftStatus, HttpMethod, InvocationLog, MembershipRole, OperationId,
|
||||
OperationSecurityLevel, OperationStatus, PlatformApiKey, PlatformApiKeyId, PlatformApiKeyKind,
|
||||
PlatformApiKeyScope, PlatformApiKeyStatus, Protocol, RestTarget, RetryPolicy, Samples,
|
||||
SecretId, Target, ToolDescription, ToolExample, User, UserId, UserSessionId, WizardState,
|
||||
Workspace, WorkspaceId,
|
||||
AgentId, AgentOperationBinding, AgentStatus, AgentVersion, ApiKeyHeaderAuthConfig,
|
||||
ApprovalRequest, ApprovalRequestId, ApprovalRequestStatus, AuthConfig, AuthKind, AuthProfile,
|
||||
ConfigExport, ExecutionConfig, ExportMode, GeneratedDraft, GeneratedDraftStatus, HttpMethod,
|
||||
InvocationLog, MembershipRole, OperationApprovalRiskLevel, OperationId, OperationSecurityLevel,
|
||||
OperationStatus, PlatformApiKey, PlatformApiKeyId, PlatformApiKeyKind, 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};
|
||||
@@ -20,13 +20,13 @@ use sqlx::{Executor, PgPool, postgres::PgPoolOptions};
|
||||
use time::{OffsetDateTime, format_description::well_known::Rfc3339};
|
||||
|
||||
use crank_registry::{
|
||||
CreateAgentRequest, CreateInvocationLogRequest, CreatePlatformApiKeyRequest,
|
||||
CreateVersionRequest, CreateWorkspaceRequest, CreateYamlImportJobRequest, DescriptorKind,
|
||||
DescriptorMetadata, OperationSampleMetadata, PlatformApiKeyRecord, PostgresRegistry,
|
||||
PublishAgentRequest, PublishRequest, RegistryError, RegistryOperation, SampleKind,
|
||||
SaveAuthProfileRequest, SaveDescriptorMetadataRequest, SaveSampleMetadataRequest,
|
||||
UpdateWorkspaceRequest, WorkspaceRecord, YamlImportJobCompletion, YamlImportJobId,
|
||||
YamlImportJobStatus,
|
||||
CreateAgentRequest, CreateApprovalRequest, CreateInvocationLogRequest,
|
||||
CreatePlatformApiKeyRequest, CreateVersionRequest, CreateWorkspaceRequest,
|
||||
CreateYamlImportJobRequest, DecideApprovalRequest, DescriptorKind, DescriptorMetadata,
|
||||
OperationSampleMetadata, PlatformApiKeyRecord, PostgresRegistry, PublishAgentRequest,
|
||||
PublishRequest, RegistryError, RegistryOperation, SampleKind, SaveAuthProfileRequest,
|
||||
SaveDescriptorMetadataRequest, SaveSampleMetadataRequest, UpdateWorkspaceRequest,
|
||||
WorkspaceRecord, YamlImportJobCompletion, YamlImportJobId, YamlImportJobStatus,
|
||||
};
|
||||
|
||||
fn test_workspace_id() -> WorkspaceId {
|
||||
@@ -455,3 +455,126 @@ async fn manages_platform_api_key_read_paths() {
|
||||
|
||||
database.cleanup().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn manages_approval_request_lifecycle() {
|
||||
let database = TestDatabase::new().await;
|
||||
let registry = database.registry().await;
|
||||
let workspace_id = test_workspace_id();
|
||||
let operation = test_operation("op_approval_01", 1, OperationStatus::Draft);
|
||||
let agent = test_agent("agent_approval_01", AgentStatus::Draft);
|
||||
let version = test_agent_version(&agent.id, 1, AgentStatus::Draft);
|
||||
let approval_key = PlatformApiKey {
|
||||
id: PlatformApiKeyId::new("key_approval_01"),
|
||||
workspace_id: workspace_id.clone(),
|
||||
agent_id: Some(agent.id.clone()),
|
||||
key_kind: PlatformApiKeyKind::Approval,
|
||||
name: "Approval key".to_owned(),
|
||||
prefix: "crk_appr".to_owned(),
|
||||
scopes: vec![PlatformApiKeyScope::Approve, PlatformApiKeyScope::Deny],
|
||||
status: PlatformApiKeyStatus::Active,
|
||||
created_at: timestamp("2026-03-25T12:00:00Z"),
|
||||
last_used_at: None,
|
||||
expires_at: None,
|
||||
allowed_origins: Vec::new(),
|
||||
};
|
||||
let approval = ApprovalRequest {
|
||||
id: ApprovalRequestId::new("approval_01"),
|
||||
workspace_id: workspace_id.clone(),
|
||||
agent_id: agent.id.clone(),
|
||||
operation_id: operation.id.clone(),
|
||||
operation_version: 1,
|
||||
status: ApprovalRequestStatus::Pending,
|
||||
risk_level: OperationApprovalRiskLevel::Dangerous,
|
||||
confirmation_title: "Confirm action".to_owned(),
|
||||
confirmation_body: "Check payload before running.".to_owned(),
|
||||
request_payload: json!({"amount": 100}),
|
||||
response_payload: None,
|
||||
created_at: timestamp("2026-03-25T12:01:00Z"),
|
||||
expires_at: timestamp("2027-03-25T12:06:00Z"),
|
||||
decided_at: None,
|
||||
decided_by_key_id: None,
|
||||
decision_note: None,
|
||||
};
|
||||
|
||||
registry
|
||||
.create_operation(&workspace_id, &operation, None)
|
||||
.await
|
||||
.unwrap();
|
||||
registry
|
||||
.create_agent(CreateAgentRequest {
|
||||
agent: &agent,
|
||||
version: &version,
|
||||
bindings: &[],
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
registry
|
||||
.create_platform_api_key(CreatePlatformApiKeyRequest {
|
||||
api_key: &approval_key,
|
||||
secret_hash: "approval-secret-hash",
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
registry
|
||||
.create_approval_request(CreateApprovalRequest {
|
||||
approval: &approval,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let pending = registry
|
||||
.list_pending_approval_requests_for_agent(&workspace_id, &agent.id)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(pending.len(), 1);
|
||||
assert_eq!(pending[0].approval, approval);
|
||||
|
||||
let decided = registry
|
||||
.decide_approval_request(DecideApprovalRequest {
|
||||
workspace_id: &workspace_id,
|
||||
agent_id: &agent.id,
|
||||
approval_id: &approval.id,
|
||||
status: ApprovalRequestStatus::Approved,
|
||||
decided_at: timestamp("2026-03-25T12:02:00Z"),
|
||||
decided_by_key_id: &approval_key.id,
|
||||
response_payload: Some(json!({"approve": "yes"})),
|
||||
decision_note: Some("confirmed in test"),
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(decided.approval.status, ApprovalRequestStatus::Approved);
|
||||
assert_eq!(
|
||||
decided.approval.decided_by_key_id,
|
||||
Some(approval_key.id.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
decided.approval.response_payload,
|
||||
Some(json!({"approve": "yes"}))
|
||||
);
|
||||
|
||||
let pending_after_decision = registry
|
||||
.list_pending_approval_requests_for_agent(&workspace_id, &agent.id)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(pending_after_decision.is_empty());
|
||||
|
||||
let repeated_decision = registry
|
||||
.decide_approval_request(DecideApprovalRequest {
|
||||
workspace_id: &workspace_id,
|
||||
agent_id: &agent.id,
|
||||
approval_id: &approval.id,
|
||||
status: ApprovalRequestStatus::Denied,
|
||||
decided_at: timestamp("2026-03-25T12:03:00Z"),
|
||||
decided_by_key_id: &approval_key.id,
|
||||
response_payload: Some(json!({"approve": "no"})),
|
||||
decision_note: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(repeated_decision.is_none());
|
||||
|
||||
database.cleanup().await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user