mod common;
use common::run_lua;
use wiremock::matchers::{header, method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
#[tokio::test]
async fn test_require_unleash() {
let script = r#"
local unleash = require("assay.unleash")
assert.not_nil(unleash)
assert.not_nil(unleash.client)
assert.not_nil(unleash.wait)
assert.not_nil(unleash.ensure_project)
assert.not_nil(unleash.ensure_environment)
assert.not_nil(unleash.ensure_token)
"#;
run_lua(script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_health() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/health"))
.and(header("Authorization", "*:*.test-admin-token"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"health": "GOOD"
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "*:*.test-admin-token" }})
local h = c.health:check()
assert.eq(h.health, "GOOD")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_projects() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/admin/projects"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"version": 1,
"projects": [
{"id": "default", "name": "Default", "description": "Default project", "memberCount": 1, "featureCount": 5},
{"id": "demo-project", "name": "Demo Project", "description": "Demo project description", "memberCount": 2, "featureCount": 3}
]
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local projects = c.projects:list()
assert.eq(#projects, 2)
assert.eq(projects[1].id, "default")
assert.eq(projects[2].id, "demo-project")
assert.eq(projects[2].name, "Demo Project")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_project() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/admin/projects/demo-project"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"id": "demo-project",
"name": "Demo Project",
"description": "Demo project description",
"environments": [
{"environment": "development", "enabled": true},
{"environment": "production", "enabled": true}
],
"features": []
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local p = c.projects:get("demo-project")
assert.eq(p.id, "demo-project")
assert.eq(p.name, "Demo Project")
assert.eq(#p.environments, 2)
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_project_not_found() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/admin/projects/nonexistent"))
.respond_with(ResponseTemplate::new(404))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local p = c.projects:get("nonexistent")
assert.eq(p, nil)
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_create_project() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/api/admin/projects"))
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
"id": "demo-project",
"name": "Demo Project",
"description": "Demo project description"
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local p = c.projects:create({{ id = "demo-project", name = "Demo Project", description = "Demo project description" }})
assert.eq(p.id, "demo-project")
assert.eq(p.name, "Demo Project")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_update_project() {
let server = MockServer::start().await;
Mock::given(method("PUT"))
.and(path("/api/admin/projects/demo-project"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"id": "demo-project",
"name": "Demo Project Updated",
"description": "Updated description"
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local p = c.projects:update("demo-project", {{ name = "Demo Project Updated", description = "Updated description" }})
assert.eq(p.name, "Demo Project Updated")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_delete_project() {
let server = MockServer::start().await;
Mock::given(method("DELETE"))
.and(path("/api/admin/projects/demo-project"))
.respond_with(ResponseTemplate::new(200))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
c.projects:delete("demo-project")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_environments() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/admin/environments"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"version": 1,
"environments": [
{"name": "development", "type": "development", "enabled": true, "sortOrder": 1},
{"name": "production", "type": "production", "enabled": true, "sortOrder": 2}
]
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local envs = c.environments:list()
assert.eq(#envs, 2)
assert.eq(envs[1].name, "development")
assert.eq(envs[2].name, "production")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_enable_environment() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/api/admin/projects/demo-project/environments"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
c.environments:enable("demo-project", "production")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_disable_environment() {
let server = MockServer::start().await;
Mock::given(method("DELETE"))
.and(path("/api/admin/projects/demo-project/environments/staging"))
.respond_with(ResponseTemplate::new(200))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
c.environments:disable("demo-project", "staging")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_features() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/admin/projects/demo-project/features"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"version": 2,
"features": [
{"name": "dark-mode", "type": "release", "enabled": false, "project": "demo-project"},
{"name": "new-dashboard", "type": "experiment", "enabled": true, "project": "demo-project"}
]
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local features = c.features:list("demo-project")
assert.eq(#features, 2)
assert.eq(features[1].name, "dark-mode")
assert.eq(features[2].name, "new-dashboard")
assert.eq(features[2].enabled, true)
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_feature() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/admin/projects/demo-project/features/dark-mode"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"name": "dark-mode",
"type": "release",
"project": "demo-project",
"enabled": false,
"environments": [
{"name": "development", "enabled": true},
{"name": "production", "enabled": false}
]
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local f = c.features:get("demo-project", "dark-mode")
assert.eq(f.name, "dark-mode")
assert.eq(f.type, "release")
assert.eq(#f.environments, 2)
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_feature_not_found() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/admin/projects/demo-project/features/nonexistent"))
.respond_with(ResponseTemplate::new(404))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local f = c.features:get("demo-project", "nonexistent")
assert.eq(f, nil)
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_create_feature() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/api/admin/projects/demo-project/features"))
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
"name": "dark-mode",
"type": "release",
"project": "demo-project",
"description": "Enable dark mode UI"
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local f = c.features:create("demo-project", {{ name = "dark-mode", type = "release", description = "Enable dark mode UI" }})
assert.eq(f.name, "dark-mode")
assert.eq(f.type, "release")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_update_feature() {
let server = MockServer::start().await;
Mock::given(method("PUT"))
.and(path("/api/admin/projects/demo-project/features/dark-mode"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"name": "dark-mode",
"type": "release",
"description": "Updated dark mode"
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local f = c.features:update("demo-project", "dark-mode", {{ description = "Updated dark mode" }})
assert.eq(f.description, "Updated dark mode")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_archive_feature() {
let server = MockServer::start().await;
Mock::given(method("DELETE"))
.and(path("/api/admin/projects/demo-project/features/dark-mode"))
.respond_with(ResponseTemplate::new(200))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
c.features:archive("demo-project", "dark-mode")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_toggle_on() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path(
"/api/admin/projects/demo-project/features/dark-mode/environments/development/on",
))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
c.features:toggle_on("demo-project", "dark-mode", "development")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_toggle_off() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path(
"/api/admin/projects/demo-project/features/dark-mode/environments/production/off",
))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
c.features:toggle_off("demo-project", "dark-mode", "production")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_strategies() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path(
"/api/admin/projects/demo-project/features/dark-mode/environments/development/strategies",
))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([
{"id": "strategy-1", "name": "default", "parameters": {}},
{"id": "strategy-2", "name": "userWithId", "parameters": {"userIds": "user1,user2"}}
])))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local strats = c.strategies:list("demo-project", "dark-mode", "development")
assert.eq(#strats, 2)
assert.eq(strats[1].name, "default")
assert.eq(strats[2].name, "userWithId")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_add_strategy() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path(
"/api/admin/projects/demo-project/features/dark-mode/environments/development/strategies",
))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"id": "strategy-3",
"name": "flexibleRollout",
"parameters": {"rollout": "50", "stickiness": "default"}
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local s = c.strategies:add("demo-project", "dark-mode", "development", {{
name = "flexibleRollout",
parameters = {{ rollout = "50", stickiness = "default" }}
}})
assert.eq(s.name, "flexibleRollout")
assert.eq(s.id, "strategy-3")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_tokens() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/admin/api-tokens"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"tokens": [
{"secret": "*:development.abc123", "tokenName": "demo-project-dev", "type": "client", "environment": "development", "projects": ["demo-project"]},
{"secret": "*:*.admin456", "tokenName": "admin", "type": "admin", "projects": ["*"]}
]
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local tokens = c.tokens:list()
assert.eq(#tokens, 2)
assert.eq(tokens[1].tokenName, "demo-project-dev")
assert.eq(tokens[1].type, "client")
assert.eq(tokens[2].type, "admin")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_create_token() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/api/admin/api-tokens"))
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
"secret": "demo-project:development.newtoken789",
"tokenName": "demo-project-client",
"type": "client",
"environment": "development",
"projects": ["demo-project"],
"createdAt": "2026-02-20T00:00:00Z"
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local t = c.tokens:create({{
tokenName = "demo-project-client",
type = "client",
environment = "development",
projects = {{ "demo-project" }}
}})
assert.eq(t.secret, "demo-project:development.newtoken789")
assert.eq(t.tokenName, "demo-project-client")
assert.eq(t.type, "client")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_delete_token() {
let server = MockServer::start().await;
Mock::given(method("DELETE"))
.and(path("/api/admin/api-tokens/old-token-secret"))
.respond_with(ResponseTemplate::new(200))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
c.tokens:delete("old-token-secret")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_wait_success() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/health"))
.respond_with(
ResponseTemplate::new(200).set_body_json(serde_json::json!({"health": "GOOD"})),
)
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local result = unleash.wait("{}", {{ timeout = 5, interval = 0.1 }})
assert.eq(result, true)
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_wait_timeout() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/health"))
.respond_with(ResponseTemplate::new(503))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
unleash.wait("{}", {{ timeout = 1, interval = 0.5 }})
"#,
server.uri()
);
let result = run_lua(&script).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_unleash_ensure_project_existing() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/admin/projects/demo-project"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"id": "demo-project",
"name": "Demo Project",
"description": "Existing project"
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local p = unleash.ensure_project(c, "demo-project")
assert.eq(p.id, "demo-project")
assert.eq(p.name, "Demo Project")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_ensure_project_new() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/admin/projects/new-project"))
.respond_with(ResponseTemplate::new(404))
.mount(&server)
.await;
Mock::given(method("POST"))
.and(path("/api/admin/projects"))
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
"id": "new-project",
"name": "New Project",
"description": "A new project"
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local p = unleash.ensure_project(c, "new-project", {{ name = "New Project", description = "A new project" }})
assert.not_nil(p)
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_ensure_environment_new() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/api/admin/projects/demo-project/environments"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local result = unleash.ensure_environment(c, "demo-project", "qa")
assert.eq(result, true)
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_ensure_environment_already_exists() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/api/admin/projects/demo-project/environments"))
.respond_with(ResponseTemplate::new(409).set_body_string("Environment already enabled"))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local result = unleash.ensure_environment(c, "demo-project", "development")
assert.eq(result, true)
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_ensure_token_existing() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/admin/api-tokens"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"tokens": [
{"secret": "hidden", "tokenName": "demo-project-dev", "type": "client", "environment": "development", "projects": ["demo-project"]}
]
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local t = unleash.ensure_token(c, {{
tokenName = "demo-project-dev",
type = "client",
environment = "development"
}})
assert.eq(t.tokenName, "demo-project-dev")
assert.eq(t.type, "client")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_unleash_ensure_token_new() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/admin/api-tokens"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"tokens": []
})))
.mount(&server)
.await;
Mock::given(method("POST"))
.and(path("/api/admin/api-tokens"))
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
"secret": "demo-project:production.newtoken",
"tokenName": "demo-project-prod",
"type": "client",
"environment": "production",
"projects": ["demo-project"]
})))
.mount(&server)
.await;
let script = format!(
r#"
local unleash = require("assay.unleash")
local c = unleash.client("{}", {{ token = "test-token" }})
local t = unleash.ensure_token(c, {{
tokenName = "demo-project-prod",
type = "client",
environment = "production",
projects = {{ "demo-project" }}
}})
assert.eq(t.secret, "demo-project:production.newtoken")
assert.eq(t.tokenName, "demo-project-prod")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}