# Tests for scalar-input validation guards # # Ensures that malformed (non-scalar) inputs to OAuthClient and OAuthProvider # constructors produce clear, package-classified errors instead of base R # subscript-out-of-bounds or coercion failures. # ── OAuthClient: client_assertion_alg ──────────────────────────────────────── test_that("oauth_client rejects character(0) client_assertion_alg", { prov <- make_test_provider() expect_error( oauth_client( provider = prov, client_id = "abc", client_secret = paste(rep("s", 32), collapse = ""), redirect_uri = "http://localhost:8100", client_assertion_alg = character(0) ), "client_assertion_alg must be a scalar" ) }) test_that("oauth_client rejects multi-element client_assertion_alg", { prov <- make_test_provider() expect_error( oauth_client( provider = prov, client_id = "abc", client_secret = paste(rep("s", 32), collapse = ""), redirect_uri = "http://localhost:8100", client_assertion_alg = c("RS256", "ES256") ), "client_assertion_alg must be a scalar" ) }) test_that("oauth_client rejects NA client_assertion_alg when explicitly provided", { # NA_character_ is valid as the default (NULL maps to it), but passing # an explicit NA via the constructor property should be caught. prov <- make_test_provider() # When passed through oauth_client(), NULL maps to NA_character_ which is # the "not provided" sentinel; explicit NA should still pass (it means # "not configured"). This test verifies no crash occurs. cli <- oauth_client( provider = prov, client_id = "abc", client_secret = "", redirect_uri = "http://localhost:8100", client_assertion_alg = NULL ) expect_s3_class(cli, "shinyOAuth::OAuthClient") }) # ── OAuthClient: client_assertion_audience ─────────────────────────────────── test_that("oauth_client rejects character(0) client_assertion_audience", { prov <- make_test_provider() expect_error( oauth_client( provider = prov, client_id = "abc", client_secret = "", redirect_uri = "http://localhost:8100", client_assertion_audience = character(0) ), "client_assertion_audience must be a scalar" ) }) test_that("oauth_client rejects multi-element client_assertion_audience", { prov <- make_test_provider() expect_error( oauth_client( provider = prov, client_id = "abc", client_secret = "", redirect_uri = "http://localhost:8100", client_assertion_audience = c("a", "b") ), "client_assertion_audience must be a scalar" ) }) test_that("oauth_client rejects empty string client_assertion_audience", { prov <- make_test_provider() expect_error( oauth_client( provider = prov, client_id = "abc", client_secret = "", redirect_uri = "http://localhost:8100", client_assertion_audience = "" ), "client_assertion_audience must be non-empty" ) }) test_that("oauth_client accepts NULL client_assertion_audience (sentinel path)", { prov <- make_test_provider() cli <- oauth_client( provider = prov, client_id = "abc", client_secret = "", redirect_uri = "http://localhost:8100", client_assertion_audience = NULL ) expect_s3_class(cli, "shinyOAuth::OAuthClient") }) # ── OAuthProvider: pkce_method ─────────────────────────────────────────────── test_that("oauth_provider rejects vector pkce_method", { expect_error( oauth_provider( name = "test", auth_url = "https://example.com/auth", token_url = "https://example.com/token", pkce_method = c("S256", "plain") ), "pkce_method must be a scalar character string" ) }) test_that("oauth_provider accepts NULL pkce_method (defaults to S256)", { p <- oauth_provider( name = "test", auth_url = "https://example.com/auth", token_url = "https://example.com/token", pkce_method = NULL ) expect_identical(p@pkce_method, "S256") }) test_that("oauth_provider accepts NA pkce_method (defaults to S256)", { p <- oauth_provider( name = "test", auth_url = "https://example.com/auth", token_url = "https://example.com/token", pkce_method = NA_character_ ) expect_identical(p@pkce_method, "S256") }) # ── OAuthProvider: URL inputs ──────────────────────────────────────────────── test_that("oauth_provider rejects vector auth_url", { expect_error( oauth_provider( name = "test", auth_url = c("https://a.com/auth", "https://b.com/auth"), token_url = "https://example.com/token" ), "auth_url must be a scalar character string" ) }) test_that("oauth_provider rejects vector token_url", { expect_error( oauth_provider( name = "test", auth_url = "https://example.com/auth", token_url = c("https://a.com/token", "https://b.com/token") ), "token_url must be a scalar character string" ) }) test_that("oauth_provider rejects vector userinfo_url", { expect_error( oauth_provider( name = "test", auth_url = "https://example.com/auth", token_url = "https://example.com/token", userinfo_url = c("https://a.com/u", "https://b.com/u") ), "userinfo_url must be a scalar character string" ) }) test_that("oauth_provider rejects vector introspection_url", { expect_error( oauth_provider( name = "test", auth_url = "https://example.com/auth", token_url = "https://example.com/token", introspection_url = c("https://a.com/i", "https://b.com/i") ), "introspection_url must be a scalar character string" ) }) test_that("oauth_provider rejects vector revocation_url", { expect_error( oauth_provider( name = "test", auth_url = "https://example.com/auth", token_url = "https://example.com/token", revocation_url = c("https://a.com/r", "https://b.com/r") ), "revocation_url must be a scalar character string" ) }) # ── JWT helpers: defense-in-depth ──────────────────────────────────────────── test_that("build_client_assertion handles zero-length client_assertion_alg gracefully", { prov <- oauth_provider( name = "test", auth_url = "https://example.com/auth", token_url = "https://example.com/token", token_auth_style = "client_secret_jwt", id_token_required = FALSE, id_token_validation = FALSE ) cli <- oauth_client( provider = prov, client_id = "abc", client_secret = paste(rep("s", 32), collapse = ""), redirect_uri = "http://localhost:8100", client_assertion_alg = NULL # maps to NA_character_ sentinel ) # Should pick default HS256 for client_secret_jwt without crashing jwt <- shinyOAuth:::build_client_assertion(cli, "https://example.com/token") expect_type(jwt, "character") expect_true(nzchar(jwt)) }) test_that("resolve_client_assertion_audience handles NA sentinel without crash", { prov <- oauth_provider( name = "test", auth_url = "https://example.com/auth", token_url = "https://example.com/token", token_auth_style = "body", id_token_required = FALSE, id_token_validation = FALSE ) cli <- oauth_client( provider = prov, client_id = "abc", client_secret = "", redirect_uri = "http://localhost:8100", client_assertion_audience = NULL ) # Should fall back to token_url aud <- shinyOAuth:::resolve_client_assertion_audience(cli, NULL) expect_identical(aud, "https://example.com/token") })