mod common;
use common::run_lua;
use wiremock::matchers::{method, path, query_param};
use wiremock::{Mock, MockServer, ResponseTemplate};
#[tokio::test]
async fn test_keto_require() {
let script = r#"
local keto = require("assay.ory.keto")
assert.not_nil(keto)
assert.not_nil(keto.client)
"#;
run_lua(script).await.unwrap();
}
#[tokio::test]
async fn test_keto_tuples_list() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/relation-tuples"))
.and(query_param("namespace", "Role"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"relation_tuples": [
{
"namespace": "Role",
"object": "namespace1:role-a",
"relation": "members",
"subject_id": "user:alice"
}
],
"next_page_token": ""
})))
.mount(&server)
.await;
let script = format!(
r#"
local keto = require("assay.ory.keto")
local k = keto.client("{}")
local result = k.tuples:list({{ namespace = "Role" }})
assert.eq(#result.relation_tuples, 1)
assert.eq(result.relation_tuples[1].object, "namespace1:role-a")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_keto_permissions_check_allowed() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/relation-tuples/check"))
.respond_with(
ResponseTemplate::new(200).set_body_json(serde_json::json!({ "allowed": true })),
)
.mount(&server)
.await;
let script = format!(
r#"
local keto = require("assay.ory.keto")
local k = keto.client("{}")
local ok = k.permissions:check("apps", "cc", "admin", "user:alice")
assert.eq(ok, true)
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_keto_permissions_check_denied() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/relation-tuples/check"))
.respond_with(
ResponseTemplate::new(200).set_body_json(serde_json::json!({ "allowed": false })),
)
.mount(&server)
.await;
let script = format!(
r#"
local keto = require("assay.ory.keto")
local k = keto.client("{}")
local ok = k.permissions:check("apps", "cc", "admin", "user:bob")
assert.eq(ok, false)
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_keto_roles_user_roles() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/relation-tuples"))
.and(query_param("namespace", "Role"))
.and(query_param("relation", "members"))
.and(query_param("subject_id", "user:alice"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"relation_tuples": [
{
"namespace": "Role",
"object": "namespace1:role-a",
"relation": "members",
"subject_id": "user:alice"
},
{
"namespace": "Role",
"object": "namespace2:role-a",
"relation": "members",
"subject_id": "user:alice"
}
]
})))
.mount(&server)
.await;
let script = format!(
r#"
local keto = require("assay.ory.keto")
local k = keto.client("{}")
local roles = k.roles:user_roles("alice")
assert.eq(#roles, 2)
assert.eq(roles[1].object, "namespace1:role-a")
assert.eq(roles[2].object, "namespace2:role-a")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_keto_roles_has_any() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/relation-tuples"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"relation_tuples": [
{
"namespace": "Role",
"object": "namespace2:role-b",
"relation": "members",
"subject_id": "user:bob"
}
]
})))
.mount(&server)
.await;
let script = format!(
r#"
local keto = require("assay.ory.keto")
local k = keto.client("{}")
local has_admin = k.roles:has_any("bob", {{"namespace1:role-a", "namespace2:role-a"}})
assert.eq(has_admin, false)
local has_op = k.roles:has_any("bob", {{"namespace2:role-b"}})
assert.eq(has_op, true)
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_keto_permissions_expand() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/relation-tuples/expand"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"type": "union",
"tuple": {
"namespace": "Role",
"object": "namespace1:role-a",
"relation": "members"
},
"children": []
})))
.mount(&server)
.await;
let script = format!(
r#"
local keto = require("assay.ory.keto")
local k = keto.client("{}")
local tree = k.permissions:expand("Role", "namespace1:role-a", "members")
assert.eq(tree.type, "union")
"#,
server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_keto_tuples_create() {
let read_server = MockServer::start().await;
let write_server = MockServer::start().await;
Mock::given(method("PUT"))
.and(path("/admin/relation-tuples"))
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({})))
.mount(&write_server)
.await;
let script = format!(
r#"
local keto = require("assay.ory.keto")
local k = keto.client("{}", {{ write_url = "{}" }})
k.tuples:create({{
namespace = "Role",
object = "namespace2:role-c",
relation = "members",
subject_id = "user:carol",
}})
"#,
read_server.uri(),
write_server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_keto_write_requires_write_url() {
let read_server = MockServer::start().await;
let script = format!(
r#"
local keto = require("assay.ory.keto")
local k = keto.client("{}")
local ok, err = pcall(function()
k.tuples:create({{ namespace = "Role", object = "x", relation = "members", subject_id = "user:y" }})
end)
assert.eq(ok, false)
assert.contains(tostring(err), "write_url not configured")
"#,
read_server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_keto_tuples_upsert_created_when_absent() {
let read_server = MockServer::start().await;
let write_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/relation-tuples"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"relation_tuples": [],
"next_page_token": ""
})))
.mount(&read_server)
.await;
Mock::given(method("PUT"))
.and(path("/admin/relation-tuples"))
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({})))
.mount(&write_server)
.await;
let script = format!(
r#"
local keto = require("assay.ory.keto")
local k = keto.client("{}", {{ write_url = "{}" }})
local action, dups = k.tuples:upsert({{
namespace = "Role",
object = "namespace1:role-a",
relation = "members",
subject_id = "user:alice",
}})
assert.eq(action, "created")
assert.eq(dups, 0)
"#,
read_server.uri(),
write_server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_keto_tuples_upsert_noop_when_one_exists() {
let read_server = MockServer::start().await;
let write_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/relation-tuples"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"relation_tuples": [
{
"namespace": "Role",
"object": "namespace1:role-a",
"relation": "members",
"subject_id": "user:alice"
}
],
"next_page_token": ""
})))
.mount(&read_server)
.await;
let script = format!(
r#"
local keto = require("assay.ory.keto")
local k = keto.client("{}", {{ write_url = "{}" }})
local action, dups = k.tuples:upsert({{
namespace = "Role",
object = "namespace1:role-a",
relation = "members",
subject_id = "user:alice",
}})
assert.eq(action, "noop")
assert.eq(dups, 0)
"#,
read_server.uri(),
write_server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_keto_tuples_upsert_repairs_duplicates() {
let read_server = MockServer::start().await;
let write_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/relation-tuples"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"relation_tuples": [
{"namespace": "Role", "object": "x", "relation": "members", "subject_id": "user:dup"},
{"namespace": "Role", "object": "x", "relation": "members", "subject_id": "user:dup"},
{"namespace": "Role", "object": "x", "relation": "members", "subject_id": "user:dup"},
{"namespace": "Role", "object": "x", "relation": "members", "subject_id": "user:dup"}
],
"next_page_token": ""
})))
.mount(&read_server)
.await;
Mock::given(method("DELETE"))
.and(path("/admin/relation-tuples"))
.respond_with(ResponseTemplate::new(204))
.mount(&write_server)
.await;
Mock::given(method("PUT"))
.and(path("/admin/relation-tuples"))
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({})))
.mount(&write_server)
.await;
let script = format!(
r#"
local keto = require("assay.ory.keto")
local k = keto.client("{}", {{ write_url = "{}" }})
local action, dups = k.tuples:upsert({{
namespace = "Role",
object = "x",
relation = "members",
subject_id = "user:dup",
}})
assert.eq(action, "repaired")
assert.eq(dups, 3)
"#,
read_server.uri(),
write_server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_keto_tuples_upsert_with_subject_set() {
let read_server = MockServer::start().await;
let write_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/relation-tuples"))
.and(query_param("namespace", "managementplane"))
.and(query_param("object", "na"))
.and(query_param("relation", "parent"))
.and(query_param("subject_set.namespace", "organization"))
.and(query_param("subject_set.object", "simons"))
.and(query_param("subject_set.relation", ""))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"relation_tuples": [
{"namespace": "managementplane", "object": "na", "relation": "parent",
"subject_set": {"namespace": "organization", "object": "simons", "relation": ""}},
{"namespace": "managementplane", "object": "na", "relation": "parent",
"subject_set": {"namespace": "organization", "object": "simons", "relation": ""}}
],
"next_page_token": ""
})))
.mount(&read_server)
.await;
Mock::given(method("DELETE"))
.and(path("/admin/relation-tuples"))
.and(query_param("subject_set.namespace", "organization"))
.and(query_param("subject_set.object", "simons"))
.respond_with(ResponseTemplate::new(204))
.mount(&write_server)
.await;
Mock::given(method("PUT"))
.and(path("/admin/relation-tuples"))
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({})))
.mount(&write_server)
.await;
let script = format!(
r#"
local keto = require("assay.ory.keto")
local k = keto.client("{}", {{ write_url = "{}" }})
local action, dups = k.tuples:upsert({{
namespace = "managementplane",
object = "na",
relation = "parent",
subject_set = {{ namespace = "organization", object = "simons", relation = "" }},
}})
assert.eq(action, "repaired")
assert.eq(dups, 1)
"#,
read_server.uri(),
write_server.uri()
);
run_lua(&script).await.unwrap();
}
#[tokio::test]
async fn test_keto_tuples_upsert_requires_write_url() {
let read_server = MockServer::start().await;
let script = format!(
r#"
local keto = require("assay.ory.keto")
local k = keto.client("{}")
local ok, err = pcall(function()
k.tuples:upsert({{ namespace = "Role", object = "x", relation = "members", subject_id = "user:y" }})
end)
assert.eq(ok, false)
assert.contains(tostring(err), "write_url not configured")
"#,
read_server.uri()
);
run_lua(&script).await.unwrap();
}