Files
crank/crates/crank-registry/tests/integration/workspace_access.rs
T
github-ops a31862aeeb
Deploy / deploy (push) Successful in 1m35s
CI / Rust Checks (push) Failing after 5m44s
CI / UI Checks (push) Has been skipped
CI / Frontend E2E (push) Has been skipped
CI / Deployment Manifests (push) Has been skipped
Refactor large Rust integration tests
2026-06-21 07:12:33 +00:00

374 lines
12 KiB
Rust

#![allow(dead_code, unused_imports)]
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, 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};
use serde_json::json;
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,
WorkspaceRecord, YamlImportJobCompletion, YamlImportJobId, YamlImportJobStatus,
};
fn test_workspace_id() -> WorkspaceId {
WorkspaceId::new("ws_default")
}
fn timestamp(value: &str) -> OffsetDateTime {
OffsetDateTime::parse(value, &Rfc3339).unwrap()
}
#[tokio::test]
async fn stores_and_finishes_yaml_import_jobs() {
let database = TestDatabase::new().await;
let registry = database.registry().await;
let job_id = YamlImportJobId::new("job_yaml_01");
let operation = test_operation("op_rest_04", 1, OperationStatus::Draft);
registry
.create_operation(&test_workspace_id(), &operation, None)
.await
.unwrap();
registry
.create_yaml_import_job(CreateYamlImportJobRequest {
id: &job_id,
source_sample_id: None,
format_version: "v1",
mode: ExportMode::Portable,
created_at: &timestamp("2026-03-25T12:00:00Z"),
})
.await
.unwrap();
registry
.finish_yaml_import_job(
&job_id,
&YamlImportJobCompletion {
status: YamlImportJobStatus::Completed,
result_operation_id: Some(operation.id.clone()),
result_version: Some(2),
error_text: None,
finished_at: timestamp("2026-03-25T12:05:00Z"),
},
)
.await
.unwrap();
let job = registry
.get_yaml_import_job(&job_id)
.await
.unwrap()
.unwrap();
assert_eq!(job.status, YamlImportJobStatus::Completed);
assert_eq!(job.result_version, Some(2));
assert_eq!(job.mode, ExportMode::Portable);
database.cleanup().await;
}
#[tokio::test]
async fn manages_workspace_read_paths() {
let database = TestDatabase::new().await;
let registry = database.registry().await;
let workspace = Workspace {
id: WorkspaceId::new("ws_extra_01"),
slug: "extra".to_owned(),
display_name: "Extra Workspace".to_owned(),
status: crank_core::WorkspaceStatus::Active,
settings: json!({"region":"eu"}),
created_at: timestamp("2026-03-25T12:00:00Z"),
updated_at: timestamp("2026-03-25T12:00:00Z"),
};
let mut user = User {
id: UserId::new("user_extra_01"),
email: "owner@example.com".to_owned(),
display_name: "Owner".to_owned(),
status: crank_core::UserStatus::Active,
created_at: timestamp("2026-03-25T11:00:00Z"),
};
registry
.create_workspace(CreateWorkspaceRequest {
workspace: &workspace,
})
.await
.unwrap();
let user_id = registry
.upsert_bootstrap_user(&user.email, &user.display_name, "hashed-password")
.await
.unwrap();
user.id = user_id;
registry
.ensure_membership(&workspace.id, &user.id, MembershipRole::Owner)
.await
.unwrap();
let all_workspaces = registry.list_workspaces().await.unwrap();
let user_workspaces = registry.list_workspaces_for_user(&user.id).await.unwrap();
let memberships = registry.list_memberships(&workspace.id).await.unwrap();
let loaded = registry
.get_workspace(&workspace.id)
.await
.unwrap()
.unwrap();
assert!(
all_workspaces
.iter()
.any(|record| record.workspace == workspace)
);
assert_eq!(user_workspaces.len(), 1);
assert_eq!(user_workspaces[0].workspace, workspace);
assert_eq!(user_workspaces[0].role, MembershipRole::Owner);
assert_eq!(memberships.len(), 1);
assert_eq!(memberships[0].workspace_id, workspace.id);
assert_eq!(memberships[0].user.id, user.id);
assert_eq!(memberships[0].user.email, user.email);
assert_eq!(memberships[0].user.display_name, user.display_name);
assert_eq!(memberships[0].user.status, user.status);
assert_eq!(memberships[0].role, MembershipRole::Owner);
assert_eq!(loaded, WorkspaceRecord { workspace });
database.cleanup().await;
}
#[tokio::test]
async fn manages_user_profile_and_workspace_access_reads() {
let database = TestDatabase::new().await;
let registry = database.registry().await;
let workspace = Workspace {
id: WorkspaceId::new("ws_profile_01"),
slug: "profile".to_owned(),
display_name: "Profile Workspace".to_owned(),
status: crank_core::WorkspaceStatus::Active,
settings: json!({}),
created_at: timestamp("2026-03-25T12:00:00Z"),
updated_at: timestamp("2026-03-25T12:00:00Z"),
};
let other_workspace = Workspace {
id: WorkspaceId::new("ws_profile_02"),
slug: "profile-other".to_owned(),
display_name: "Other Workspace".to_owned(),
status: crank_core::WorkspaceStatus::Active,
settings: json!({}),
created_at: timestamp("2026-03-25T12:01:00Z"),
updated_at: timestamp("2026-03-25T12:01:00Z"),
};
let user_id = registry
.upsert_bootstrap_user("profile@example.com", "Owner", "hashed-password")
.await
.unwrap();
registry
.create_workspace(CreateWorkspaceRequest {
workspace: &workspace,
})
.await
.unwrap();
registry
.create_workspace(CreateWorkspaceRequest {
workspace: &other_workspace,
})
.await
.unwrap();
registry
.ensure_membership(&workspace.id, &user_id, MembershipRole::Owner)
.await
.unwrap();
let updated = registry
.update_user_profile(&user_id, "updated@example.com", "Updated Owner")
.await
.unwrap();
let has_access = registry
.user_has_workspace_access(&user_id, &workspace.id)
.await
.unwrap();
let lacks_access = registry
.user_has_workspace_access(&user_id, &other_workspace.id)
.await
.unwrap();
assert_eq!(updated.id, user_id);
assert_eq!(updated.email, "updated@example.com");
assert_eq!(updated.display_name, "Updated Owner");
assert!(has_access);
assert!(!lacks_access);
assert!(updated.created_at.unix_timestamp() > 0);
database.cleanup().await;
}
#[tokio::test]
async fn creates_and_loads_user_sessions_with_typed_expiration() {
let database = TestDatabase::new().await;
let registry = database.registry().await;
let workspace = Workspace {
id: WorkspaceId::new("ws_session_01"),
slug: "session".to_owned(),
display_name: "Session Workspace".to_owned(),
status: crank_core::WorkspaceStatus::Active,
settings: json!({}),
created_at: timestamp("2026-03-25T12:00:00Z"),
updated_at: timestamp("2026-03-25T12:00:00Z"),
};
let user_id = registry
.upsert_bootstrap_user("session@example.com", "Owner", "hashed-password")
.await
.unwrap();
let session_id = UserSessionId::new("sess_01");
let expires_at = timestamp("2030-04-06T12:05:00Z");
registry
.create_workspace(CreateWorkspaceRequest {
workspace: &workspace,
})
.await
.unwrap();
registry
.ensure_membership(&workspace.id, &user_id, MembershipRole::Owner)
.await
.unwrap();
registry
.create_user_session(
&session_id,
&user_id,
Some(&workspace.id),
"secret-hash-01",
&expires_at,
)
.await
.unwrap();
let session = registry
.get_user_session(&session_id, "secret-hash-01")
.await
.unwrap()
.unwrap();
assert_eq!(session.session_id, session_id);
assert_eq!(session.current_workspace_id, Some(workspace.id.clone()));
assert_eq!(session.user.id, user_id);
assert!(session.user.created_at.unix_timestamp() > 0);
database.cleanup().await;
}
#[tokio::test]
async fn manages_platform_api_key_read_paths() {
let database = TestDatabase::new().await;
let registry = database.registry().await;
let workspace = Workspace {
id: WorkspaceId::new("ws_keys_01"),
slug: "keys".to_owned(),
display_name: "Keys Workspace".to_owned(),
status: crank_core::WorkspaceStatus::Active,
settings: json!({}),
created_at: timestamp("2026-03-25T12:00:00Z"),
updated_at: timestamp("2026-03-25T12:00:00Z"),
};
let agent = test_agent("agent_keys_01", AgentStatus::Draft);
let version = test_agent_version(&agent.id, 1, AgentStatus::Draft);
let api_key = PlatformApiKey {
id: PlatformApiKeyId::new("key_01"),
workspace_id: workspace.id.clone(),
agent_id: Some(agent.id.clone()),
name: "Primary".to_owned(),
prefix: "crk_live".to_owned(),
scopes: vec![PlatformApiKeyScope::Read, PlatformApiKeyScope::Write],
status: PlatformApiKeyStatus::Active,
created_at: timestamp("2026-03-25T12:01:00Z"),
last_used_at: None,
};
let secret_hash = "secret_hash_01";
registry
.create_workspace(CreateWorkspaceRequest {
workspace: &workspace,
})
.await
.unwrap();
registry
.create_agent(CreateAgentRequest {
agent: &agent,
version: &version,
bindings: &[],
})
.await
.unwrap();
registry
.create_platform_api_key(CreatePlatformApiKeyRequest {
api_key: &api_key,
secret_hash,
})
.await
.unwrap();
let listed = registry
.list_platform_api_keys(&workspace.id)
.await
.unwrap();
let listed_for_agent = registry
.list_platform_api_keys_for_agent(&workspace.id, &agent.id)
.await
.unwrap();
assert_eq!(
listed,
vec![PlatformApiKeyRecord {
api_key: api_key.clone()
}]
);
assert_eq!(
listed_for_agent,
vec![PlatformApiKeyRecord {
api_key: api_key.clone()
}]
);
let resolved = registry
.get_platform_api_key_by_secret_for_agent_slug(&workspace.slug, &agent.slug, secret_hash)
.await
.unwrap()
.unwrap();
assert_eq!(resolved.api_key, api_key);
registry
.touch_platform_api_key(
&workspace.id,
&PlatformApiKeyId::new("key_01"),
&timestamp("2026-03-25T12:05:00Z"),
)
.await
.unwrap();
let touched = registry
.list_platform_api_keys(&workspace.id)
.await
.unwrap();
assert_eq!(
touched[0].api_key.last_used_at,
Some(timestamp("2026-03-25T12:05:00Z"))
);
database.cleanup().await;
}