wit 0.16.0

A tool for building and publishing WIT packages to a registry.
Documentation
use std::fs;

use anyhow::Result;
use assert_cmd::prelude::*;
use predicates::{prelude::PredicateBooleanExt, str::contains, Predicate};

use crate::support::*;

mod support;

#[test]
fn help() {
    for arg in ["help update", "update -h", "update --help"] {
        wit(arg.split_whitespace())
            .assert()
            .stdout(contains("Update dependencies as recorded in the lock file"))
            .success();
    }
}

#[test]
fn it_fails_with_missing_toml_file() -> Result<()> {
    wit(["update"])
        .assert()
        .stderr(contains(
            "error: failed to find configuration file `wit.toml`",
        ))
        .failure();
    Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn update_without_changes_is_a_noop() -> Result<()> {
    let (server, _, _) = spawn_server(Vec::<String>::new()).await?;

    let project = server.project("bar", Vec::<String>::new())?;
    project.file("bar.wit", "package test:bar;\n")?;
    project
        .wit(["publish"])
        .env("WIT_PUBLISH_KEY", test_signing_key())
        .assert()
        .stderr(contains("Published package `test:bar` v0.1.0"))
        .success();

    let project = server.project("baz", Vec::<String>::new())?;
    project.file("baz.wit", "package test:baz;\n")?;
    project
        .wit(["add", "test:bar"])
        .assert()
        .stderr(contains("Added dependency `test:bar` with version `0.1.0"))
        .success();

    project
        .wit(["build"])
        .assert()
        .success()
        .stderr(contains("Created package `baz.wasm`"));

    project
        .wit(["update"])
        .assert()
        .success()
        .stderr(contains("test:bar").not());

    Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_update_without_compatible_changes_is_a_noop() -> Result<()> {
    let (server, _, _) = spawn_server(Vec::<String>::new()).await?;

    let project1 = server.project("bar", Vec::<String>::new())?;
    project1.file("bar.wit", "package test:bar;\n")?;

    project1
        .wit(["publish"])
        .env("WIT_PUBLISH_KEY", test_signing_key())
        .assert()
        .stderr(contains("Published package `test:bar` v0.1.0"))
        .success();

    let project2 = server.project("baz", Vec::<String>::new())?;
    project2.file("baz.wit", "package test:baz;\n")?;
    project2
        .wit(["add", "test:bar"])
        .assert()
        .stderr(contains("Added dependency `test:bar` with version `0.1.0"))
        .success();

    project2
        .wit(["build"])
        .assert()
        .success()
        .stderr(contains("Created package `baz.wasm`"));

    project1.file(
        "wit.toml",
        "version = \"1.0.0\"\n[dependencies]\n[registries]\n",
    )?;

    project1
        .wit(["publish"])
        .env("WIT_PUBLISH_KEY", test_signing_key())
        .assert()
        .stderr(contains("Published package `test:bar` v1.0.0"))
        .success();

    project2
        .wit(["update"])
        .assert()
        .success()
        .stderr(contains("test:bar").not());

    Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn update_with_compatible_changes() -> Result<()> {
    let (server, _, _) = spawn_server(Vec::<String>::new()).await?;

    let project1 = server.project("bar", Vec::<String>::new())?;
    project1.file("bar.wit", "package test:bar;\n")?;
    project1.file(
        "wit.toml",
        "version = \"1.0.0\"\n[dependencies]\n[registries]\n",
    )?;

    project1
        .wit(["publish"])
        .env("WIT_PUBLISH_KEY", test_signing_key())
        .assert()
        .stderr(contains("Published package `test:bar` v1.0.0"))
        .success();

    let project2 = server.project("baz", Vec::<String>::new())?;
    project2.file("baz.wit", "package test:baz;\n")?;
    project2
        .wit(["add", "test:bar"])
        .assert()
        .stderr(contains("Added dependency `test:bar` with version `1.0.0"))
        .success();

    project2
        .wit(["build"])
        .assert()
        .success()
        .stderr(contains("Created package `baz.wasm`"));

    project1.file(
        "wit.toml",
        "version = \"1.1.0\"\n[dependencies]\n[registries]\n",
    )?;

    project1
        .wit(["publish"])
        .env("WIT_PUBLISH_KEY", test_signing_key())
        .assert()
        .stderr(contains("Published package `test:bar` v1.1.0"))
        .success();

    project2
        .wit(["update"])
        .assert()
        .success()
        .stderr(contains("Updating dependency `test:bar` v1.0.0 -> v1.1.0"));

    let lock_file = fs::read_to_string(project2.root().join("wit.lock"))?;
    assert!(contains("version = \"1.1.0\"").eval(&lock_file));

    Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn update_with_compatible_changes_is_noop_for_dryrun() -> Result<()> {
    let (server, _, _) = spawn_server(Vec::<String>::new()).await?;

    let project1 = server.project("bar", Vec::<String>::new())?;
    project1.file("bar.wit", "package test:bar;\n")?;
    project1.file(
        "wit.toml",
        "version = \"1.0.0\"\n[dependencies]\n[registries]\n",
    )?;

    project1
        .wit(["publish"])
        .env("WIT_PUBLISH_KEY", test_signing_key())
        .assert()
        .stderr(contains("Published package `test:bar` v1.0.0"))
        .success();

    let project2 = server.project("baz", Vec::<String>::new())?;
    project2.file("baz.wit", "package test:baz;\n")?;
    project2
        .wit(["add", "test:bar"])
        .assert()
        .stderr(contains("Added dependency `test:bar` with version `1.0.0"))
        .success();

    project2
        .wit(["build"])
        .assert()
        .success()
        .stderr(contains("Created package `baz.wasm`"));

    project1.file(
        "wit.toml",
        "version = \"1.1.0\"\n[dependencies]\n[registries]\n",
    )?;

    project1
        .wit(["publish"])
        .env("WIT_PUBLISH_KEY", test_signing_key())
        .assert()
        .stderr(contains("Published package `test:bar` v1.1.0"))
        .success();

    project2
        .wit(["update", "--dry-run"])
        .assert()
        .success()
        .stderr(
            contains("Would update dependency `test:bar` v1.0.0 -> v1.1.0").and(contains(
                "warning: not updating lock file due to --dry-run option",
            )),
        );

    let lock_file = fs::read_to_string(project2.root().join("wit.lock"))?;
    assert!(contains("version = \"1.1.0\"").not().eval(&lock_file));

    Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn update_with_changed_dependencies() -> Result<()> {
    let (server, _, _) = spawn_server(Vec::<String>::new()).await?;

    let project = server.project("bar", Vec::<String>::new())?;
    project.file("bar.wit", "package test:bar;\n")?;
    project.file(
        "wit.toml",
        "version = \"1.0.0\"\n[dependencies]\n[registries]\n",
    )?;

    project
        .wit(["publish"])
        .env("WIT_PUBLISH_KEY", test_signing_key())
        .assert()
        .stderr(contains("Published package `test:bar` v1.0.0"))
        .success();

    let project = server.project("baz", Vec::<String>::new())?;
    project.file("baz.wit", "package test:baz;\n")?;
    project.file(
        "wit.toml",
        "version = \"1.0.0\"\n[dependencies]\n[registries]\n",
    )?;

    project
        .wit(["publish"])
        .env("WIT_PUBLISH_KEY", test_signing_key())
        .assert()
        .stderr(contains("Published package `test:baz` v1.0.0"))
        .success();

    let project = server.project("qux", Vec::<String>::new())?;
    project.file("qux.wit", "package test:qux;\n")?;
    project
        .wit(["add", "test:bar"])
        .assert()
        .stderr(contains("Added dependency `test:bar` with version `1.0.0"))
        .success();

    project
        .wit(["build"])
        .assert()
        .stderr(contains("Created package `qux.wasm`"))
        .success();

    project.file(
        "wit.toml",
        "version = \"1.0.0\"\n[dependencies]\n\"test:baz\" = \"1.0.0\"\n[registries]\n",
    )?;

    project
        .wit(["update"])
        .assert()
        .stderr(
            contains("Removing dependency `test:bar` v1.0.0")
                .and(contains("Adding dependency `test:baz` v1.0.0")),
        )
        .success();

    project
        .wit(["build"])
        .assert()
        .stderr(contains("Created package `qux.wasm`"))
        .success();

    let path = project.root().join("qux.wasm");
    validate_component(&path)?;

    Ok(())
}