working on workflows
This commit is contained in:
@@ -438,3 +438,38 @@ pub async fn mock_not_found(server: &MockServer, path_pattern: &str) {
|
||||
.mount(server)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Mock a successful pack create response (POST /api/v1/packs)
|
||||
#[allow(dead_code)]
|
||||
pub async fn mock_pack_create(server: &MockServer) {
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/packs"))
|
||||
.respond_with(ResponseTemplate::new(201).set_body_json(json!({
|
||||
"data": {
|
||||
"id": 42,
|
||||
"ref": "my_pack",
|
||||
"label": "My Pack",
|
||||
"description": "A test pack",
|
||||
"version": "0.1.0",
|
||||
"author": null,
|
||||
"enabled": true,
|
||||
"tags": ["test"],
|
||||
"created": "2024-01-01T00:00:00Z",
|
||||
"updated": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
})))
|
||||
.mount(server)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Mock a 409 conflict response for pack create
|
||||
#[allow(dead_code)]
|
||||
pub async fn mock_pack_create_conflict(server: &MockServer) {
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/packs"))
|
||||
.respond_with(ResponseTemplate::new(409).set_body_json(json!({
|
||||
"error": "Pack with ref 'my_pack' already exists"
|
||||
})))
|
||||
.mount(server)
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
|
||||
use assert_cmd::Command;
|
||||
use predicates::prelude::*;
|
||||
use serde_json::json;
|
||||
use wiremock::{
|
||||
matchers::{body_json, method, path},
|
||||
Mock, ResponseTemplate,
|
||||
};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
@@ -222,6 +227,231 @@ async fn test_pack_get_json_output() {
|
||||
.stdout(predicate::str::contains(r#""ref": "core""#));
|
||||
}
|
||||
|
||||
// ── pack create tests ──────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_pack_create_non_interactive() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
mock_pack_create(&fixture.mock_server).await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("pack")
|
||||
.arg("create")
|
||||
.arg("--ref")
|
||||
.arg("my_pack")
|
||||
.arg("--label")
|
||||
.arg("My Pack")
|
||||
.arg("--description")
|
||||
.arg("A test pack")
|
||||
.arg("--pack-version")
|
||||
.arg("0.1.0")
|
||||
.arg("--tags")
|
||||
.arg("test");
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("my_pack"))
|
||||
.stdout(predicate::str::contains("created successfully"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_pack_create_json_output() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
mock_pack_create(&fixture.mock_server).await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("--json")
|
||||
.arg("pack")
|
||||
.arg("create")
|
||||
.arg("--ref")
|
||||
.arg("my_pack");
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains(r#""ref": "my_pack""#))
|
||||
.stdout(predicate::str::contains(r#""id": 42"#));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_pack_create_conflict() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
mock_pack_create_conflict(&fixture.mock_server).await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("pack")
|
||||
.arg("create")
|
||||
.arg("--ref")
|
||||
.arg("my_pack");
|
||||
|
||||
cmd.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("already exists"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_pack_create_missing_ref() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("pack")
|
||||
.arg("create");
|
||||
|
||||
cmd.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("Pack ref is required"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_pack_create_default_label_from_ref() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
// Use a custom mock that validates the request body contains the derived label
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/packs"))
|
||||
.and(body_json(json!({
|
||||
"ref": "my_cool_pack",
|
||||
"label": "My Cool Pack",
|
||||
"version": "0.1.0",
|
||||
"tags": []
|
||||
})))
|
||||
.respond_with(ResponseTemplate::new(201).set_body_json(json!({
|
||||
"data": {
|
||||
"id": 99,
|
||||
"ref": "my_cool_pack",
|
||||
"label": "My Cool Pack",
|
||||
"version": "0.1.0",
|
||||
"enabled": true,
|
||||
"created": "2024-01-01T00:00:00Z",
|
||||
"updated": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
})))
|
||||
.mount(&fixture.mock_server)
|
||||
.await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("pack")
|
||||
.arg("create")
|
||||
.arg("--ref")
|
||||
.arg("my_cool_pack");
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("my_cool_pack"))
|
||||
.stdout(predicate::str::contains("created successfully"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_pack_create_default_version() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
// Verify the default version "0.1.0" is sent when --pack-version is not specified
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/packs"))
|
||||
.and(body_json(json!({
|
||||
"ref": "versioned_pack",
|
||||
"label": "Versioned Pack",
|
||||
"version": "0.1.0",
|
||||
"tags": []
|
||||
})))
|
||||
.respond_with(ResponseTemplate::new(201).set_body_json(json!({
|
||||
"data": {
|
||||
"id": 7,
|
||||
"ref": "versioned_pack",
|
||||
"label": "Versioned Pack",
|
||||
"version": "0.1.0",
|
||||
"enabled": true,
|
||||
"created": "2024-01-01T00:00:00Z",
|
||||
"updated": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
})))
|
||||
.mount(&fixture.mock_server)
|
||||
.await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("pack")
|
||||
.arg("create")
|
||||
.arg("--ref")
|
||||
.arg("versioned_pack");
|
||||
|
||||
cmd.assert().success();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_pack_create_with_tags() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/packs"))
|
||||
.and(body_json(json!({
|
||||
"ref": "tagged",
|
||||
"label": "Tagged",
|
||||
"version": "0.1.0",
|
||||
"tags": ["networking", "monitoring"]
|
||||
})))
|
||||
.respond_with(ResponseTemplate::new(201).set_body_json(json!({
|
||||
"data": {
|
||||
"id": 10,
|
||||
"ref": "tagged",
|
||||
"label": "Tagged",
|
||||
"version": "0.1.0",
|
||||
"tags": ["networking", "monitoring"],
|
||||
"enabled": true,
|
||||
"created": "2024-01-01T00:00:00Z",
|
||||
"updated": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
})))
|
||||
.mount(&fixture.mock_server)
|
||||
.await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("pack")
|
||||
.arg("create")
|
||||
.arg("--ref")
|
||||
.arg("tagged")
|
||||
.arg("--tags")
|
||||
.arg("networking,monitoring");
|
||||
|
||||
cmd.assert().success();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_pack_list_empty_result() {
|
||||
let fixture = TestFixture::new().await;
|
||||
|
||||
777
crates/cli/tests/test_workflows.rs
Normal file
777
crates/cli/tests/test_workflows.rs
Normal file
@@ -0,0 +1,777 @@
|
||||
//! Integration tests for CLI workflow commands
|
||||
|
||||
#![allow(deprecated)]
|
||||
|
||||
use assert_cmd::Command;
|
||||
use predicates::prelude::*;
|
||||
use serde_json::json;
|
||||
use std::fs;
|
||||
use wiremock::matchers::{method, path};
|
||||
use wiremock::{Mock, MockServer, ResponseTemplate};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
|
||||
// ── Mock helpers ────────────────────────────────────────────────────────
|
||||
|
||||
async fn mock_workflow_list(server: &MockServer) {
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/api/v1/workflows"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"ref": "core.install_packs",
|
||||
"pack_ref": "core",
|
||||
"label": "Install Packs",
|
||||
"description": "Install one or more packs",
|
||||
"version": "1.0.0",
|
||||
"tags": ["core", "packs"],
|
||||
"enabled": true,
|
||||
"created": "2024-01-01T00:00:00Z",
|
||||
"updated": "2024-01-01T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"ref": "mypack.deploy",
|
||||
"pack_ref": "mypack",
|
||||
"label": "Deploy App",
|
||||
"description": "Deploy an application",
|
||||
"version": "2.0.0",
|
||||
"tags": ["deploy"],
|
||||
"enabled": true,
|
||||
"created": "2024-01-02T00:00:00Z",
|
||||
"updated": "2024-01-02T00:00:00Z"
|
||||
}
|
||||
]
|
||||
})))
|
||||
.mount(server)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn mock_workflow_list_by_pack(server: &MockServer, pack_ref: &str) {
|
||||
let p = format!("/api/v1/packs/{}/workflows", pack_ref);
|
||||
Mock::given(method("GET"))
|
||||
.and(path(p.as_str()))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"ref": format!("{}.example_workflow", pack_ref),
|
||||
"pack_ref": pack_ref,
|
||||
"label": "Example Workflow",
|
||||
"description": "An example workflow",
|
||||
"version": "1.0.0",
|
||||
"tags": [],
|
||||
"enabled": true,
|
||||
"created": "2024-01-01T00:00:00Z",
|
||||
"updated": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
})))
|
||||
.mount(server)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn mock_workflow_get(server: &MockServer, workflow_ref: &str) {
|
||||
let p = format!("/api/v1/workflows/{}", workflow_ref);
|
||||
Mock::given(method("GET"))
|
||||
.and(path(p.as_str()))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"data": {
|
||||
"id": 1,
|
||||
"ref": workflow_ref,
|
||||
"pack": 1,
|
||||
"pack_ref": "mypack",
|
||||
"label": "My Workflow",
|
||||
"description": "A test workflow",
|
||||
"version": "1.0.0",
|
||||
"param_schema": {
|
||||
"url": {"type": "string", "required": true},
|
||||
"timeout": {"type": "integer", "default": 30}
|
||||
},
|
||||
"out_schema": {
|
||||
"status": {"type": "string"}
|
||||
},
|
||||
"definition": {
|
||||
"version": "1.0.0",
|
||||
"vars": {"result": null},
|
||||
"tasks": [
|
||||
{
|
||||
"name": "step1",
|
||||
"action": "core.echo",
|
||||
"input": {"message": "hello"},
|
||||
"next": [
|
||||
{"when": "{{ succeeded() }}", "do": ["step2"]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "step2",
|
||||
"action": "core.echo",
|
||||
"input": {"message": "done"}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tags": ["test", "demo"],
|
||||
"enabled": true,
|
||||
"created": "2024-01-01T00:00:00Z",
|
||||
"updated": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
})))
|
||||
.mount(server)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn mock_workflow_delete(server: &MockServer, workflow_ref: &str) {
|
||||
let p = format!("/api/v1/workflows/{}", workflow_ref);
|
||||
Mock::given(method("DELETE"))
|
||||
.and(path(p.as_str()))
|
||||
.respond_with(ResponseTemplate::new(204))
|
||||
.mount(server)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn mock_workflow_save(server: &MockServer, pack_ref: &str) {
|
||||
let p = format!("/api/v1/packs/{}/workflow-files", pack_ref);
|
||||
Mock::given(method("POST"))
|
||||
.and(path(p.as_str()))
|
||||
.respond_with(ResponseTemplate::new(201).set_body_json(json!({
|
||||
"data": {
|
||||
"id": 10,
|
||||
"ref": format!("{}.deploy", pack_ref),
|
||||
"pack": 1,
|
||||
"pack_ref": pack_ref,
|
||||
"label": "Deploy App",
|
||||
"description": "Deploy the application",
|
||||
"version": "1.0.0",
|
||||
"param_schema": null,
|
||||
"out_schema": null,
|
||||
"definition": {"version": "1.0.0", "tasks": []},
|
||||
"tags": ["deploy"],
|
||||
"enabled": true,
|
||||
"created": "2024-01-10T00:00:00Z",
|
||||
"updated": "2024-01-10T00:00:00Z"
|
||||
}
|
||||
})))
|
||||
.mount(server)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn mock_workflow_save_conflict(server: &MockServer, pack_ref: &str) {
|
||||
let p = format!("/api/v1/packs/{}/workflow-files", pack_ref);
|
||||
Mock::given(method("POST"))
|
||||
.and(path(p.as_str()))
|
||||
.respond_with(ResponseTemplate::new(409).set_body_json(json!({
|
||||
"error": "Workflow with ref 'mypack.deploy' already exists"
|
||||
})))
|
||||
.mount(server)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn mock_workflow_update(server: &MockServer, workflow_ref: &str) {
|
||||
let p = format!("/api/v1/workflows/{}/file", workflow_ref);
|
||||
Mock::given(method("PUT"))
|
||||
.and(path(p.as_str()))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"data": {
|
||||
"id": 10,
|
||||
"ref": workflow_ref,
|
||||
"pack": 1,
|
||||
"pack_ref": "mypack",
|
||||
"label": "Deploy App",
|
||||
"description": "Deploy the application",
|
||||
"version": "1.0.0",
|
||||
"param_schema": null,
|
||||
"out_schema": null,
|
||||
"definition": {"version": "1.0.0", "tasks": []},
|
||||
"tags": ["deploy"],
|
||||
"enabled": true,
|
||||
"created": "2024-01-10T00:00:00Z",
|
||||
"updated": "2024-01-10T12:00:00Z"
|
||||
}
|
||||
})))
|
||||
.mount(server)
|
||||
.await;
|
||||
}
|
||||
|
||||
// ── Helper to write action + workflow YAML to temp dirs ─────────────────
|
||||
|
||||
struct WorkflowFixture {
|
||||
_dir: tempfile::TempDir,
|
||||
action_yaml_path: String,
|
||||
}
|
||||
|
||||
impl WorkflowFixture {
|
||||
fn new(action_ref: &str, workflow_file: &str) -> Self {
|
||||
let dir = tempfile::TempDir::new().expect("Failed to create temp dir");
|
||||
let actions_dir = dir.path().join("actions");
|
||||
let workflows_dir = actions_dir.join("workflows");
|
||||
fs::create_dir_all(&workflows_dir).unwrap();
|
||||
|
||||
// Write the action YAML
|
||||
let action_yaml = format!(
|
||||
r#"ref: {}
|
||||
label: "Deploy App"
|
||||
description: "Deploy the application"
|
||||
enabled: true
|
||||
workflow_file: {}
|
||||
|
||||
parameters:
|
||||
environment:
|
||||
type: string
|
||||
required: true
|
||||
description: "Target environment"
|
||||
version:
|
||||
type: string
|
||||
default: "latest"
|
||||
|
||||
output:
|
||||
status:
|
||||
type: string
|
||||
|
||||
tags:
|
||||
- deploy
|
||||
"#,
|
||||
action_ref, workflow_file,
|
||||
);
|
||||
|
||||
let action_name = action_ref.rsplit('.').next().unwrap();
|
||||
let action_path = actions_dir.join(format!("{}.yaml", action_name));
|
||||
fs::write(&action_path, &action_yaml).unwrap();
|
||||
|
||||
// Write the workflow YAML
|
||||
let workflow_yaml = r#"version: "1.0.0"
|
||||
|
||||
vars:
|
||||
deploy_result: null
|
||||
|
||||
tasks:
|
||||
- name: prepare
|
||||
action: core.echo
|
||||
input:
|
||||
message: "Preparing deployment"
|
||||
next:
|
||||
- when: "{{ succeeded() }}"
|
||||
do:
|
||||
- deploy
|
||||
|
||||
- name: deploy
|
||||
action: core.echo
|
||||
input:
|
||||
message: "Deploying"
|
||||
next:
|
||||
- when: "{{ succeeded() }}"
|
||||
do:
|
||||
- verify
|
||||
|
||||
- name: verify
|
||||
action: core.echo
|
||||
input:
|
||||
message: "Verifying"
|
||||
|
||||
output_map:
|
||||
status: "{{ 'success' if workflow.deploy_result else 'unknown' }}"
|
||||
"#;
|
||||
|
||||
let workflow_path = workflows_dir.join(format!("{}.workflow.yaml", action_name));
|
||||
fs::write(&workflow_path, workflow_yaml).unwrap();
|
||||
|
||||
Self {
|
||||
action_yaml_path: action_path.to_string_lossy().to_string(),
|
||||
_dir: dir,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── List tests ──────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_list_authenticated() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
mock_workflow_list(&fixture.mock_server).await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("workflow")
|
||||
.arg("list");
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("core.install_packs"))
|
||||
.stdout(predicate::str::contains("mypack.deploy"))
|
||||
.stdout(predicate::str::contains("2 workflow(s) found"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_list_by_pack() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
mock_workflow_list_by_pack(&fixture.mock_server, "core").await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("workflow")
|
||||
.arg("list")
|
||||
.arg("--pack")
|
||||
.arg("core");
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("core.example_workflow"))
|
||||
.stdout(predicate::str::contains("1 workflow(s) found"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_list_json_output() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
mock_workflow_list(&fixture.mock_server).await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("--json")
|
||||
.arg("workflow")
|
||||
.arg("list");
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"core.install_packs\""))
|
||||
.stdout(predicate::str::contains("\"mypack.deploy\""));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_list_yaml_output() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
mock_workflow_list(&fixture.mock_server).await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("--yaml")
|
||||
.arg("workflow")
|
||||
.arg("list");
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("core.install_packs"))
|
||||
.stdout(predicate::str::contains("mypack.deploy"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_list_empty() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/api/v1/workflows"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"data": []
|
||||
})))
|
||||
.mount(&fixture.mock_server)
|
||||
.await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("workflow")
|
||||
.arg("list");
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("No workflows found"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_list_unauthenticated() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_default_config();
|
||||
|
||||
mock_unauthorized(&fixture.mock_server, "/api/v1/workflows").await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("workflow")
|
||||
.arg("list");
|
||||
|
||||
cmd.assert().failure();
|
||||
}
|
||||
|
||||
// ── Show tests ──────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_show() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
mock_workflow_get(&fixture.mock_server, "mypack.my_workflow").await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("workflow")
|
||||
.arg("show")
|
||||
.arg("mypack.my_workflow");
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("mypack.my_workflow"))
|
||||
.stdout(predicate::str::contains("My Workflow"))
|
||||
.stdout(predicate::str::contains("1.0.0"))
|
||||
.stdout(predicate::str::contains("test, demo"))
|
||||
// Tasks table should show task names
|
||||
.stdout(predicate::str::contains("step1"))
|
||||
.stdout(predicate::str::contains("step2"))
|
||||
.stdout(predicate::str::contains("core.echo"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_show_json_output() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
mock_workflow_get(&fixture.mock_server, "mypack.my_workflow").await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("--json")
|
||||
.arg("workflow")
|
||||
.arg("show")
|
||||
.arg("mypack.my_workflow");
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"mypack.my_workflow\""))
|
||||
.stdout(predicate::str::contains("\"My Workflow\""))
|
||||
.stdout(predicate::str::contains("\"definition\""));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_show_not_found() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
mock_not_found(&fixture.mock_server, "/api/v1/workflows/nonexistent.wf").await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("workflow")
|
||||
.arg("show")
|
||||
.arg("nonexistent.wf");
|
||||
|
||||
cmd.assert().failure();
|
||||
}
|
||||
|
||||
// ── Delete tests ────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_delete_with_yes_flag() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
mock_workflow_delete(&fixture.mock_server, "mypack.my_workflow").await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("workflow")
|
||||
.arg("delete")
|
||||
.arg("mypack.my_workflow")
|
||||
.arg("--yes");
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("deleted successfully"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_delete_json_output() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
mock_workflow_delete(&fixture.mock_server, "mypack.my_workflow").await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("--json")
|
||||
.arg("workflow")
|
||||
.arg("delete")
|
||||
.arg("mypack.my_workflow")
|
||||
.arg("--yes");
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"message\""))
|
||||
.stdout(predicate::str::contains("deleted"));
|
||||
}
|
||||
|
||||
// ── Upload tests ────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_upload_success() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
let wf_fixture =
|
||||
WorkflowFixture::new("mypack.deploy", "workflows/deploy.workflow.yaml");
|
||||
|
||||
mock_workflow_save(&fixture.mock_server, "mypack").await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("workflow")
|
||||
.arg("upload")
|
||||
.arg(&wf_fixture.action_yaml_path);
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("uploaded successfully"))
|
||||
.stdout(predicate::str::contains("mypack.deploy"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_upload_json_output() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
let wf_fixture =
|
||||
WorkflowFixture::new("mypack.deploy", "workflows/deploy.workflow.yaml");
|
||||
|
||||
mock_workflow_save(&fixture.mock_server, "mypack").await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("--json")
|
||||
.arg("workflow")
|
||||
.arg("upload")
|
||||
.arg(&wf_fixture.action_yaml_path);
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"mypack.deploy\""))
|
||||
.stdout(predicate::str::contains("\"Deploy App\""));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_upload_conflict_without_force() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
let wf_fixture =
|
||||
WorkflowFixture::new("mypack.deploy", "workflows/deploy.workflow.yaml");
|
||||
|
||||
mock_workflow_save_conflict(&fixture.mock_server, "mypack").await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("workflow")
|
||||
.arg("upload")
|
||||
.arg(&wf_fixture.action_yaml_path);
|
||||
|
||||
cmd.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("already exists"))
|
||||
.stderr(predicate::str::contains("--force"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_upload_conflict_with_force() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
let wf_fixture =
|
||||
WorkflowFixture::new("mypack.deploy", "workflows/deploy.workflow.yaml");
|
||||
|
||||
mock_workflow_save_conflict(&fixture.mock_server, "mypack").await;
|
||||
mock_workflow_update(&fixture.mock_server, "mypack.deploy").await;
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("workflow")
|
||||
.arg("upload")
|
||||
.arg(&wf_fixture.action_yaml_path)
|
||||
.arg("--force");
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("uploaded successfully"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_upload_missing_action_file() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("workflow")
|
||||
.arg("upload")
|
||||
.arg("/nonexistent/path/action.yaml");
|
||||
|
||||
cmd.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("not found"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_upload_missing_workflow_file() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
// Create a temp dir with only the action YAML, no workflow file
|
||||
let dir = tempfile::TempDir::new().unwrap();
|
||||
let actions_dir = dir.path().join("actions");
|
||||
fs::create_dir_all(&actions_dir).unwrap();
|
||||
|
||||
let action_yaml = r#"ref: mypack.deploy
|
||||
label: "Deploy App"
|
||||
workflow_file: workflows/deploy.workflow.yaml
|
||||
"#;
|
||||
let action_path = actions_dir.join("deploy.yaml");
|
||||
fs::write(&action_path, action_yaml).unwrap();
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("workflow")
|
||||
.arg("upload")
|
||||
.arg(action_path.to_string_lossy().as_ref());
|
||||
|
||||
cmd.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("Workflow file not found"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_upload_action_without_workflow_file_field() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
// Create a temp dir with a regular (non-workflow) action YAML
|
||||
let dir = tempfile::TempDir::new().unwrap();
|
||||
let actions_dir = dir.path().join("actions");
|
||||
fs::create_dir_all(&actions_dir).unwrap();
|
||||
|
||||
let action_yaml = r#"ref: mypack.echo
|
||||
label: "Echo"
|
||||
description: "A regular action, not a workflow"
|
||||
runner_type: shell
|
||||
entry_point: echo.sh
|
||||
"#;
|
||||
let action_path = actions_dir.join("echo.yaml");
|
||||
fs::write(&action_path, action_yaml).unwrap();
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("workflow")
|
||||
.arg("upload")
|
||||
.arg(action_path.to_string_lossy().as_ref());
|
||||
|
||||
cmd.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("workflow_file"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_upload_invalid_action_yaml() {
|
||||
let fixture = TestFixture::new().await;
|
||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||
|
||||
let dir = tempfile::TempDir::new().unwrap();
|
||||
let bad_yaml_path = dir.path().join("bad.yaml");
|
||||
fs::write(&bad_yaml_path, "this is not valid yaml: [[[").unwrap();
|
||||
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
|
||||
.env("HOME", fixture.config_dir_path())
|
||||
.arg("--api-url")
|
||||
.arg(fixture.server_url())
|
||||
.arg("workflow")
|
||||
.arg("upload")
|
||||
.arg(bad_yaml_path.to_string_lossy().as_ref());
|
||||
|
||||
cmd.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("Failed to parse action YAML"));
|
||||
}
|
||||
|
||||
// ── Help text tests ─────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_help() {
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.arg("workflow").arg("--help");
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("upload"))
|
||||
.stdout(predicate::str::contains("list"))
|
||||
.stdout(predicate::str::contains("show"))
|
||||
.stdout(predicate::str::contains("delete"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_upload_help() {
|
||||
let mut cmd = Command::cargo_bin("attune").unwrap();
|
||||
cmd.arg("workflow").arg("upload").arg("--help");
|
||||
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("action"))
|
||||
.stdout(predicate::str::contains("workflow_file"))
|
||||
.stdout(predicate::str::contains("--force"));
|
||||
}
|
||||
Reference in New Issue
Block a user