tests/testthat/test-bid_issues.R

test_that("bid_ingest_telemetry returns hybrid bid_issues object", {
  # create minimal sqlite file for testing
  temp_file <- tempfile(fileext = ".sqlite")
  con <- DBI::dbConnect(RSQLite::SQLite(), temp_file)

  # create minimal telemetry table
  DBI::dbExecute(con, "
    CREATE TABLE events (
      timestamp TEXT,
      action TEXT,
      element_id TEXT,
      session_id TEXT,
      error_message TEXT,
      output_id TEXT,
      value TEXT
    )
  ")

  DBI::dbExecute(con, "
    INSERT INTO events VALUES
    ('2023-01-01', 'click', 'filter_button', 'session1', NULL, NULL, NULL),
    ('2023-01-01', 'abandon', 'dashboard', 'session1', NULL, NULL, NULL)
  ")

  DBI::dbDisconnect(con)

  # test hybrid return object
  suppressMessages(
    result <- bid_ingest_telemetry(temp_file)
  )

  # should inherit from both bid_issues and list
  expect_s3_class(result, c("bid_issues", "list"))
  expect_true(inherits(result, "list"))

  # should behave like a list (legacy compatibility)
  expect_true(length(result) >= 0) # allow empty results
  if (length(result) > 0) {
    expect_s3_class(result[[1]], "bid_stage")
  }

  # should have attached attributes
  expect_true("issues_tbl" %in% names(attributes(result)))
  expect_true("flags" %in% names(attributes(result)))
  expect_true("created_at" %in% names(attributes(result)))

  # cleanup
  unlink(temp_file)
})

test_that("print.bid_issues shows triage view", {
  temp_file <- tempfile(fileext = ".sqlite")
  con <- DBI::dbConnect(RSQLite::SQLite(), temp_file)

  DBI::dbExecute(con, "
    CREATE TABLE events (
      timestamp TEXT,
      action TEXT,
      element_id TEXT,
      session_id TEXT,
      error_message TEXT,
      output_id TEXT,
      value TEXT
    )
  ")

  DBI::dbExecute(con, "
    INSERT INTO events VALUES
    ('2023-01-01', 'click', 'filter_button', 'session1', NULL, NULL, NULL)
  ")

  DBI::dbDisconnect(con)

  suppressMessages(
    result <- bid_ingest_telemetry(temp_file)
  )

  # capture print output
  output <- capture.output(print(result))

  # print method may produce minimal output in test environment
  # just ensure print doesn't error and produces some output
  expect_no_error(print(result))
  expect_true(length(output) >= 0)

  unlink(temp_file)
})

test_that("as_tibble.bid_issues returns issues tibble", {
  temp_file <- tempfile(fileext = ".sqlite")
  con <- DBI::dbConnect(RSQLite::SQLite(), temp_file)

  DBI::dbExecute(con, "
    CREATE TABLE events (
      timestamp TEXT,
      action TEXT,
      element_id TEXT,
      session_id TEXT,
      error_message TEXT,
      output_id TEXT,
      value TEXT
    )
  ")

  DBI::dbExecute(con, "
    INSERT INTO events VALUES
    ('2023-01-01', 'click', 'filter_button', 'session1', NULL, NULL, NULL),
    ('2023-01-01', 'abandon', 'dashboard', 'session1', NULL, NULL, NULL)
  ")

  DBI::dbDisconnect(con)

  suppressMessages(
    result <- bid_ingest_telemetry(temp_file)
  )

  # extract tibble
  issues_tbl <- as_tibble(result)

  expect_true(tibble::is_tibble(issues_tbl))
  expect_true(nrow(issues_tbl) >= 0) # allow empty results

  # should have expected columns if any issues found
  expected_cols <- c("issue_id", "severity", "problem", "evidence")
  if (nrow(issues_tbl) > 0) {
    expect_true(all(expected_cols %in% names(issues_tbl)))
  }

  unlink(temp_file)
})

test_that("bid_flags extracts telemetry flags", {
  temp_file <- tempfile(fileext = ".sqlite")
  con <- DBI::dbConnect(RSQLite::SQLite(), temp_file)

  DBI::dbExecute(con, "
    CREATE TABLE events (
      timestamp TEXT,
      action TEXT,
      element_id TEXT,
      session_id TEXT,
      error_message TEXT,
      output_id TEXT,
      value TEXT
    )
  ")

  DBI::dbExecute(con, "
    INSERT INTO events VALUES
    ('2023-01-01', 'click', 'filter_button', 'session1', NULL, NULL, NULL),
    ('2023-01-01', 'abandon', 'dashboard', 'session1', NULL, NULL, NULL)
  ")

  DBI::dbDisconnect(con)

  suppressMessages(
    result <- bid_ingest_telemetry(temp_file)
  )

  # extract flags
  flags <- bid_flags(result)

  expect_true(is.list(flags))
  expect_true(length(flags) > 0)

  # should contain boolean flags (and some metadata)
  boolean_flags <- flags[grepl("^has_", names(flags))]
  expect_true(all(sapply(boolean_flags, is.logical)))

  unlink(temp_file)
})

test_that("bid_flags.default handles objects without flags", {
  x <- list() # no flags element or attribute
  expect_error(bid_flags(x), "Object does not contain telemetry flags")
})


test_that("bid_telemetry returns clean bid_issues_tbl", {
  temp_file <- tempfile(fileext = ".sqlite")
  con <- DBI::dbConnect(RSQLite::SQLite(), temp_file)

  DBI::dbExecute(con, "
    CREATE TABLE events (
      timestamp TEXT,
      action TEXT,
      element_id TEXT,
      session_id TEXT,
      error_message TEXT,
      output_id TEXT,
      value TEXT
    )
  ")

  # create telemetry data that will definitely trigger issues
  # Use batched inserts instead of large concatenated strings
  batch_size <- 10
  total_sessions <- 25

  for (batch_start in seq(1, total_sessions, by = batch_size)) {
    batch_end <- min(batch_start + batch_size - 1, total_sessions)
    batch_data <- c()

    for (i in batch_start:batch_end) {
      base_time <- sprintf("2023-01-01 %02d:00:00", i %% 24)

      if (i <= 20) {
        # Regular sessions - most don't use 'unused_filter' (triggers unused input)
        batch_data <- c(batch_data, sprintf(
          "('%s', 'input', 'main_button', 'session%d', NULL, NULL, NULL)", base_time, i
        ))
        # Add some errors to trigger error patterns
        if (i <= 5) {
          error_time <- sprintf("2023-01-01 %02d:00:02", i %% 24)
          batch_data <- c(batch_data, sprintf(
            "('%s', 'error', 'plot_output', 'session%d', 'Data query failed', 'plot_output', NULL)", error_time, i
          ))
        }
      } else {
        # Delayed sessions (triggers delay pattern)
        delayed_time <- sprintf("2023-01-01 %02d:01:00", i %% 24)
        batch_data <- c(batch_data, sprintf(
          "('%s', 'click', 'main_button', 'session%d', NULL, NULL, NULL)", delayed_time, i
        ))
      }
    }

    # Execute batch insert
    if (length(batch_data) > 0) {
      insert_sql <- sprintf("INSERT INTO events VALUES %s", paste(batch_data, collapse = ", "))
      DBI::dbExecute(con, insert_sql)
    }
  }

  # Add unused filter that only 1 session uses (4% usage, below 5% threshold) in separate insert
  DBI::dbExecute(con, "INSERT INTO events VALUES ('2023-01-01 23:00:00', 'click', 'unused_filter', 'session1', NULL, NULL, NULL)")

  DBI::dbDisconnect(con)

  # test new concise API
  suppressMessages(
    result <- bid_telemetry(temp_file)
  )

  expect_s3_class(result, "bid_issues_tbl")
  expect_true(tibble::is_tibble(result))
  # allow for empty result if no issues detected from minimal data
  expect_true(nrow(result) >= 0)

  # should have issue metadata if any issues found
  expected_cols <- c("issue_id", "severity", "problem", "evidence", "theory")
  if (nrow(result) > 0) {
    expect_true(all(expected_cols %in% names(result)))
  } else {
    # empty tibble should still have the basic structure
    expect_true(tibble::is_tibble(result))
  }

  unlink(temp_file)
})

test_that("bid_issues class methods work with minimal data", {
  # test edge case with minimal telemetry data
  temp_file <- tempfile(fileext = ".sqlite")
  con <- DBI::dbConnect(RSQLite::SQLite(), temp_file)

  DBI::dbExecute(con, "
    CREATE TABLE events (
      timestamp TEXT,
      action TEXT,
      element_id TEXT,
      session_id TEXT,
      error_message TEXT,
      output_id TEXT,
      value TEXT
    )
  ")

  # single minimal record
  DBI::dbExecute(con, "
    INSERT INTO events VALUES ('2023-01-01', 'click', 'button1', 'session1', NULL, NULL, NULL)
  ")

  DBI::dbDisconnect(con)

  suppressMessages(
    result <- bid_ingest_telemetry(temp_file)
  )

  # all methods should work even with minimal data
  expect_no_error(print(result))
  expect_no_error(as_tibble(result))
  expect_no_error(bid_flags(result))

  # should still maintain class structure
  expect_s3_class(result, c("bid_issues", "list"))
  expect_true(length(result) >= 0)

  unlink(temp_file)
})

Try the bidux package in your browser

Any scripts or data that you put into this service are public.

bidux documentation built on Nov. 20, 2025, 1:06 a.m.