Nothing
# ============================================================================
# OTEL INGESTION TESTS
# ============================================================================
# tests for agent 1's otel data ingestion and format detection
# these tests verify otlp json and sqlite reading capabilities
test_that("detect_otel_json identifies otlp format correctly", {
skip_if_no_otel()
# create mock otlp json file
spans <- create_mock_otel_spans(sessions = 1, reactives_per_session = 2)
otlp_file <- create_temp_otel_json(spans)
# test otlp detection returns true
result <- bidux:::detect_otel_json(otlp_file)
expect_true(result)
unlink(otlp_file)
})
test_that("detect_otel_json distinguishes shiny.telemetry from otlp", {
skip_if_no_otel()
# create traditional shiny.telemetry json
telemetry_file <- create_temp_shiny_telemetry_json(sessions = 2)
# test shiny.telemetry format returns false
result <- bidux:::detect_otel_json(telemetry_file)
expect_false(result)
unlink(telemetry_file)
})
test_that("detect_otel_json handles malformed json gracefully", {
skip_if_no_otel()
# create invalid json file
bad_file <- tempfile(fileext = ".json")
writeLines(c("{", "invalid json", "}"), bad_file)
# detect_otel_json returns FALSE for malformed JSON (doesn't error)
result <- bidux:::detect_otel_json(bad_file)
expect_false(result)
unlink(bad_file)
})
test_that("detect_otel_json validates required otlp structure", {
skip_if_no_otel()
# create json without resourceSpans (not otlp)
not_otlp <- tempfile(fileext = ".json")
writeLines('{"data": [{"key": "value"}]}', not_otlp)
result <- bidux:::detect_otel_json(not_otlp)
expect_false(result)
unlink(not_otlp)
})
test_that("read_otel_json parses otlp json correctly", {
skip_if_no_otel()
# create mock otlp data
spans <- create_mock_otel_spans(
sessions = 2,
reactives_per_session = 5,
outputs_per_session = 3
)
otlp_file <- create_temp_otel_json(spans)
# read and parse
result <- bidux:::read_otel_json(otlp_file)
# verify result structure
expect_true(is.data.frame(result))
expect_gt(nrow(result), 0)
# verify expected columns exist
expected_cols <- c("timestamp", "session_id", "event_type")
expect_true(all(expected_cols %in% names(result)))
# verify span names are preserved
expect_true("login" %in% result$event_type)
expect_true("input" %in% result$event_type)
unlink(otlp_file)
})
test_that("read_otel_json extracts span attributes correctly", {
skip_if_no_otel()
spans <- create_mock_otel_spans(sessions = 1, reactives_per_session = 2)
otlp_file <- create_temp_otel_json(spans)
result <- bidux:::read_otel_json(otlp_file)
# check session_id extraction from attributes
login_events <- result[result$event_type == "login", ]
expect_gt(nrow(login_events), 0)
expect_false(all(is.na(login_events$session_id)))
unlink(otlp_file)
})
test_that("read_otel_json handles nested scopeSpans structure", {
skip_if_no_otel()
# create otlp with multiple scopeSpans (edge case)
spans <- create_mock_otel_spans(sessions = 2, reactives_per_session = 3)
otlp_file <- create_temp_otel_json(spans)
# should flatten all spans regardless of nesting
result <- bidux:::read_otel_json(otlp_file)
expect_true(is.data.frame(result))
expect_gt(nrow(result), 5) # should have multiple spans
unlink(otlp_file)
})
test_that("read_otel_json extracts span events (errors)", {
skip_if_no_otel()
# create spans with error events
spans <- create_mock_otel_spans(
sessions = 2,
outputs_per_session = 5,
include_errors = TRUE
)
otlp_file <- create_temp_otel_json(spans)
result <- bidux:::read_otel_json(otlp_file)
# verify events column exists or events are extracted
has_events <- "events" %in% names(result) || "error_message" %in% names(result)
expect_true(has_events)
unlink(otlp_file)
})
test_that("read_otel_json handles empty spans array", {
skip_if_no_otel()
# create otlp structure with no spans
empty_otlp <- tempfile(fileext = ".json")
empty_structure <- list(
resourceSpans = list(
list(
scopeSpans = list(
list(spans = list())
)
)
)
)
jsonlite::write_json(empty_structure, empty_otlp, auto_unbox = TRUE)
result <- bidux:::read_otel_json(empty_otlp)
# should return empty dataframe with proper structure
expect_true(is.data.frame(result))
expect_equal(nrow(result), 0)
unlink(empty_otlp)
})
test_that("read_otel_json validates required span fields", {
skip_if_no_otel()
# create otlp with missing required fields
malformed_otlp <- tempfile(fileext = ".json")
malformed <- list(
resourceSpans = list(
list(
scopeSpans = list(
list(
spans = list(
list(
traceId = "abc123",
# missing spanId, name, timestamps
attributes = list()
)
)
)
)
)
)
)
jsonlite::write_json(malformed, malformed_otlp, auto_unbox = TRUE)
# malformed spans are skipped gracefully, may return empty result
result <- bidux:::read_otel_json(malformed_otlp)
expect_true(is.data.frame(result))
unlink(malformed_otlp)
})
test_that("read_otel_sqlite reads otel database tables", {
skip_if_no_otel()
skip_if_no_telemetry_deps()
# create otel sqlite database
spans <- create_mock_otel_spans(sessions = 2, reactives_per_session = 5)
db_path <- create_temp_otel_sqlite(spans)
# read database
result <- bidux:::read_otel_sqlite(db_path)
# verify structure
expect_true(is.data.frame(result))
expect_gt(nrow(result), 0)
# verify expected columns
expected_cols <- c("timestamp", "session_id", "event_type")
expect_true(all(expected_cols %in% names(result)))
unlink(db_path)
})
test_that("read_otel_sqlite detects correct table names", {
skip_if_no_otel()
skip_if_no_telemetry_deps()
spans <- create_mock_otel_spans(sessions = 1)
db_path <- create_temp_otel_sqlite(spans)
# verify table detection works
con <- DBI::dbConnect(RSQLite::SQLite(), db_path)
tables <- DBI::dbListTables(con)
expect_true("spans" %in% tables)
expect_true("span_attributes" %in% tables)
DBI::dbDisconnect(con)
# verify read function finds these tables
result <- bidux:::read_otel_sqlite(db_path)
expect_true(is.data.frame(result))
unlink(db_path)
})
test_that("read_otel_sqlite joins attributes correctly", {
skip_if_no_otel()
skip_if_no_telemetry_deps()
spans <- create_mock_otel_spans(sessions = 1, reactives_per_session = 3)
db_path <- create_temp_otel_sqlite(spans)
result <- bidux:::read_otel_sqlite(db_path)
# attributes should be joined or accessible
# check for session_id or attributes column
has_attrs <- "session_id" %in% names(result) || "attributes" %in% names(result)
expect_true(has_attrs)
unlink(db_path)
})
test_that("read_otel_sqlite handles missing attributes table", {
skip_if_no_otel()
skip_if_no_telemetry_deps()
# create minimal database with only spans table
db_path <- tempfile(fileext = ".sqlite")
con <- DBI::dbConnect(RSQLite::SQLite(), db_path)
spans_table <- data.frame(
traceId = "abc123",
spanId = "span001",
parentSpanId = NA_character_,
name = "session_start",
startTimeUnixNano = "1609459200000000000",
endTimeUnixNano = "1609459200100000000",
stringsAsFactors = FALSE
)
DBI::dbWriteTable(con, "spans", spans_table)
DBI::dbDisconnect(con)
# should still read successfully (no attributes means session_id will be NA)
result <- bidux:::read_otel_sqlite(db_path)
expect_true(is.data.frame(result))
expect_true("session_id" %in% names(result))
unlink(db_path)
})
test_that("read_otel_sqlite extracts span events from events table", {
skip_if_no_otel()
skip_if_no_telemetry_deps()
# create database with error events
spans <- create_mock_otel_spans(
sessions = 2,
outputs_per_session = 5,
include_errors = TRUE
)
db_path <- create_temp_otel_sqlite(spans)
result <- bidux:::read_otel_sqlite(db_path)
# should include error information
has_errors <- "events" %in% names(result) ||
"error_message" %in% names(result) ||
any(grepl("error", names(result), ignore.case = TRUE))
expect_true(has_errors)
unlink(db_path)
})
test_that("read_otel_sqlite works with dbi connection", {
skip_if_no_otel()
skip_if_no_telemetry_deps()
spans <- create_mock_otel_spans(sessions = 1)
db_path <- create_temp_otel_sqlite(spans)
# pass connection instead of path
con <- DBI::dbConnect(RSQLite::SQLite(), db_path)
result <- bidux:::read_otel_sqlite(con)
expect_true(is.data.frame(result))
expect_gt(nrow(result), 0)
# connection should remain open (we don't own it)
expect_true(DBI::dbIsValid(con))
DBI::dbDisconnect(con)
unlink(db_path)
})
test_that("format auto-detection chooses read_otel_json for otlp", {
skip_if_no_otel()
spans <- create_mock_otel_spans(sessions = 1)
otlp_file <- create_temp_otel_json(spans)
# auto-detect should route to otel reader
format <- bidux:::detect_telemetry_format(otlp_file)
# if otel detection is implemented, should return "otel_json" or similar
# fallback: should at least return "json"
expect_true(format %in% c("json", "otel_json", "otlp"))
unlink(otlp_file)
})
test_that("format auto-detection chooses read_otel_sqlite for otel db", {
skip_if_no_otel()
skip_if_no_telemetry_deps()
spans <- create_mock_otel_spans(sessions = 1)
db_path <- create_temp_otel_sqlite(spans)
# detect format
format <- bidux:::detect_telemetry_format(db_path)
# should detect as sqlite (with otel schema detection happening later)
expect_equal(format, "sqlite")
unlink(db_path)
})
test_that("format auto-detection distinguishes otel from shiny.telemetry", {
skip_if_no_otel()
# create both formats
spans <- create_mock_otel_spans(sessions = 1)
otlp_file <- create_temp_otel_json(spans)
telemetry_file <- create_temp_shiny_telemetry_json(sessions = 1)
# both should be detected as json initially
otlp_format <- bidux:::detect_telemetry_format(otlp_file)
telemetry_format <- bidux:::detect_telemetry_format(telemetry_file)
expect_equal(otlp_format, "json")
expect_equal(telemetry_format, "json")
# but content-based detection should differ
is_otlp <- bidux:::detect_otel_json(otlp_file)
is_telemetry <- bidux:::detect_otel_json(telemetry_file)
expect_true(is_otlp)
expect_false(is_telemetry)
unlink(c(otlp_file, telemetry_file))
})
test_that("malformed otlp data produces clear error messages", {
skip_if_no_otel()
# test missing resourceSpans
bad_json1 <- tempfile(fileext = ".json")
writeLines('{"spans": []}', bad_json1)
expect_error(
bidux:::read_otel_json(bad_json1),
"resourceSpans|structure|format"
)
unlink(bad_json1)
# test invalid json
bad_json2 <- tempfile(fileext = ".json")
writeLines('{"invalid": json', bad_json2)
expect_error(
bidux:::read_otel_json(bad_json2),
"json|parse"
)
unlink(bad_json2)
})
test_that("read_otel_json handles large span counts efficiently", {
skip_if_no_otel()
skip_on_cran() # performance test with larger dataset
# create large dataset
spans <- create_mock_otel_spans(
sessions = 10,
reactives_per_session = 20,
outputs_per_session = 10
)
otlp_file <- create_temp_otel_json(spans)
# should complete without timeout or memory issues
start_time <- Sys.time()
result <- bidux:::read_otel_json(otlp_file)
elapsed <- as.numeric(difftime(Sys.time(), start_time, units = "secs"))
expect_true(is.data.frame(result))
expect_gt(nrow(result), 100) # should have many spans
expect_lt(elapsed, 5) # should complete within 5 seconds
unlink(otlp_file)
})
test_that("read_otel_json converts spans to bidux event schema", {
skip_if_no_otel()
spans <- create_mock_otel_spans(sessions = 1, reactives_per_session = 3)
otlp_file <- create_temp_otel_json(spans)
result <- bidux:::read_otel_json(otlp_file)
# should have bidux event schema columns
expect_true(is.data.frame(result))
expect_gt(nrow(result), 0)
expect_true("event_type" %in% names(result))
expect_true("timestamp" %in% names(result))
expect_true("session_id" %in% names(result))
unlink(otlp_file)
})
test_that("read_otel_json handles multiple attribute value types", {
skip_if_no_otel()
# create custom otlp with different attribute types
custom_otlp <- tempfile(fileext = ".json")
otlp_data <- list(
resourceSpans = list(
list(
scopeSpans = list(
list(
spans = list(
list(
traceId = "abc123",
spanId = "span001",
name = "test",
startTimeUnixNano = "1609459200000000000",
endTimeUnixNano = "1609459200100000000",
attributes = list(
list(key = "string_attr", value = list(stringValue = "test")),
list(key = "int_attr", value = list(intValue = 42)),
list(key = "double_attr", value = list(doubleValue = 3.14)),
list(key = "bool_attr", value = list(boolValue = TRUE))
)
)
)
)
)
)
)
)
jsonlite::write_json(otlp_data, custom_otlp, auto_unbox = TRUE)
# should handle all value types
expect_no_error({
result <- bidux:::read_otel_json(custom_otlp)
})
unlink(custom_otlp)
})
test_that("read_otel_sqlite handles concurrent access safely", {
skip_if_no_otel()
skip_if_no_telemetry_deps()
skip_on_cran()
spans <- create_mock_otel_spans(sessions = 2)
db_path <- create_temp_otel_sqlite(spans)
# multiple reads should work (read-only access)
result1 <- bidux:::read_otel_sqlite(db_path)
result2 <- bidux:::read_otel_sqlite(db_path)
expect_equal(nrow(result1), nrow(result2))
unlink(db_path)
})
test_that("otel readers handle unicode and special characters", {
skip_if_no_otel()
# create spans with unicode in attributes
spans <- create_mock_otel_spans(sessions = 1)
# modify to include unicode
spans$name[1] <- "session_start_\u2713" # checkmark
otlp_file <- create_temp_otel_json(spans)
# should handle unicode correctly
expect_no_error({
result <- bidux:::read_otel_json(otlp_file)
})
unlink(otlp_file)
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.