Skip to content

Commit

Permalink
add session cookie support
Browse files Browse the repository at this point in the history
  • Loading branch information
hillalex committed Aug 28, 2024
1 parent 88c4d14 commit dee2e0b
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 25 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Imports:
stringr,
tibble
Remotes:
reside-ic/porcelain,
hillalex/porcelain@i39,
Suggests:
fs,
lintr (>= 3.1.2),
Expand Down
39 changes: 29 additions & 10 deletions R/api.R
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ target_post_dataset <- function(req, res) {
filename <- stringr::str_remove_all(filename,
paste0(".", file_ext))
}
path <- file.path("uploads", filename)
if (file.exists(path)) {
if (is.null(req$session$id)) {
req$session$id <- rawToChar(as.raw(sample(c(65:90,97:122), 10, replace=T)))

Check warning on line 31 in R/api.R

View workflow job for this annotation

GitHub Actions / lint

file=R/api.R,line=31,col=55,[commas_linter] Commas should always have a space after.

Check warning on line 31 in R/api.R

View workflow job for this annotation

GitHub Actions / lint

file=R/api.R,line=31,col=75,[infix_spaces_linter] Put spaces around all infix operators.

Check warning on line 31 in R/api.R

View workflow job for this annotation

GitHub Actions / lint

file=R/api.R,line=31,col=77,[T_and_F_symbol_linter] Use TRUE instead of the symbol T.
}
session_id <- as.character(req$session$id)
path <- file.path("uploads", session_id, filename)
if (dir.exists(path)) {
res$status <- 400L
msg <- paste(filename, "already exists.",
"Please choose a unique name for this dataset.")
Expand All @@ -44,15 +48,15 @@ target_post_dataset <- function(req, res) {
}

logger::log_info(paste("Saving dataset", filename, "to disk"))
dir.create(path)
dir.create(path, recursive = TRUE)
utils::write.csv(file_body, file.path(path, "data"), row.names = FALSE)
write(xcol, file.path(path, "xcol"))
porcelain:::response_success(jsonlite::unbox(filename))
}

target_get_dataset <- function(name) {
target_get_dataset <- function(name, req) {
logger::log_info(paste("Requesting metadata for dataset:", name))
dataset <- read_dataset(name)
dataset <- read_dataset(req, name)
logger::log_info(paste("Found dataset:", name))
dat <- dataset$data
xcol <- dataset$xcol
Expand All @@ -78,17 +82,19 @@ target_get_dataset <- function(name) {
xcol = jsonlite::unbox(xcol))
}

target_get_datasets <- function() {
list.files("uploads")
target_get_datasets <- function(req) {
session_id <- get_or_create_session(req)
list.files(file.path("uploads", session_id))
}

target_get_trace <- function(name,
biomarker,
req,
filter = NULL,
disaggregate = NULL) {
logger::log_info(paste("Requesting data from", name,
"with biomarker", biomarker))
dataset <- read_dataset(name)
dataset <- read_dataset(req, name)
dat <- dataset$data
xcol <- dataset$xcol
cols <- colnames(dat)
Expand Down Expand Up @@ -123,8 +129,9 @@ target_get_trace <- function(name,
}
}

read_dataset <- function(name) {
path <- file.path("uploads", name)
read_dataset <- function(req, name) {
session_id <- get_or_create_session(req)
path <- file.path("uploads", session_id, name)
if (!file.exists(path)) {
porcelain::porcelain_stop(paste("Did not find dataset with name:", name),
code = "DATASET_NOT_FOUND", status_code = 404L)
Expand Down Expand Up @@ -175,3 +182,15 @@ bad_request_response <- function(msg) {
detail = msg)
return(list(status = "failure", errors = list(error), data = NULL))
}

get_or_create_session <- function(req) {
if (is.null(req$session$id)) {
logger::log_info("Creating new session id")
req$session$id <- generate_session_id()
}
as.character(req$session$id)
}

generate_session_id <- function() {
rawToChar(as.raw(sample(c(65:90,97:122), 10, replace=T)))

Check warning on line 195 in R/api.R

View workflow job for this annotation

GitHub Actions / lint

file=R/api.R,line=195,col=35,[commas_linter] Commas should always have a space after.

Check warning on line 195 in R/api.R

View workflow job for this annotation

GitHub Actions / lint

file=R/api.R,line=195,col=55,[infix_spaces_linter] Put spaces around all infix operators.

Check warning on line 195 in R/api.R

View workflow job for this annotation

GitHub Actions / lint

file=R/api.R,line=195,col=57,[T_and_F_symbol_linter] Use TRUE instead of the symbol T.
}
5 changes: 4 additions & 1 deletion R/router.R
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ build_routes <- function() {
req$HTTP_ORIGIN %in% c("http://localhost:3000", "http://localhost")) {
# allow local app and integration tests to access endpoints
res$setHeader("Access-Control-Allow-Origin", req$HTTP_ORIGIN)
res$setHeader("Access-Control-Allow-Credentials", "true")
}
value
})

pr$registerHooks(plumber::session_cookie(plumber::random_cookie_key(),
path = "/"))

pr$handle(get_root())
pr$handle(get_version())
pr$handle("POST", "/dataset/",
Expand All @@ -23,7 +27,6 @@ build_routes <- function() {
pr$handle(get_trace())
}


get_root <- function() {
porcelain::porcelain_endpoint$new(
"GET",
Expand Down
16 changes: 9 additions & 7 deletions tests/testthat/helper.R
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
set.seed(1)
session_id <- generate_session_id()

make_req <- function(verb = "GET", path = "/", qs = "", body = "", pr = NULL, ...) {

Check warning on line 4 in tests/testthat/helper.R

View workflow job for this annotation

GitHub Actions / lint

file=tests/testthat/helper.R,line=4,col=81,[line_length_linter] Lines should not be more than 80 characters. This line is 84 characters.
req <- as.environment(list(...))
req$REQUEST_METHOD <- toupper(verb)
req$PATH_INFO <- path
req$QUERY_STRING <- qs

if (is.character(body)) {
body <- charToRaw(body)
}
Expand All @@ -14,8 +16,8 @@ make_req <- function(verb = "GET", path = "/", qs = "", body = "", pr = NULL, ..
}

local_add_dataset <- function(dat, name, env = parent.frame()) {
filepath <- file.path("uploads", name)
dir.create(filepath)
filepath <- file.path("uploads", session_id, name)
dir.create(filepath, recursive = TRUE)
write.csv(dat, file.path(filepath, "data"), row.names = FALSE)
write("day", file.path(filepath, "xcol"))
withr::defer(fs::dir_delete(filepath), envir = env)
Expand All @@ -35,7 +37,7 @@ local_POST_dataset_request <- function(dat, filename, xcol = "day",
"Content-Disposition: form-data; name=\"xcol\"", EOL, EOL,
xcol, EOL,
boundary, "--")
filepath <- file.path("uploads", filename)
filepath <- file.path("uploads", session_id, filename)
withr::defer({
if (fs::file_exists(filepath)) {
fs::file_delete(filepath)
Expand All @@ -49,7 +51,7 @@ local_POST_dataset_request <- function(dat, filename, xcol = "day",
}

local_POST_dataset_request_no_xcol <- function(dat, filename,
env = parent.frame()) {
env = parent.frame()) {
EOL <- "\r\n"
boundary <- "------WebKitFormBoundaryvbfCGA1r00d8B0Vv"
request_body <- paste0(boundary, EOL,
Expand All @@ -58,7 +60,7 @@ local_POST_dataset_request_no_xcol <- function(dat, filename,
"Content-Type: text/csv", EOL, EOL,
readr::format_csv(dat, eol = EOL), EOL,
boundary, "--")
filepath <- file.path("uploads", filename)
filepath <- file.path("uploads", session_id, filename)
withr::defer({
if (fs::file_exists(filepath)) {
fs::file_delete(filepath)
Expand All @@ -84,7 +86,7 @@ local_POST_dataset_request_bad_file <- function(env = parent.frame()) {
"Content-Disposition: form-data; name=\"xcol\"", EOL, EOL,
"day", EOL,
boundary, "--")
filepath <- file.path("uploads", filename)
filepath <- file.path("uploads", session_id, filename)
withr::defer({
if (fs::file_exists(filepath)) {
fs::file_delete(filepath)
Expand Down
7 changes: 7 additions & 0 deletions tests/testthat/test-read.R
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@ test_that("can get trace for uploaded dataset with xcol", {
time = 1:10,
age = "0-5",
sex = c("M", "F"))
set.seed(1)
request <- local_POST_dataset_request(dat,
"testdata",
xcol = "time")
router <- build_routes()
res <- router$call(request)
expect_equal(res$status, 200)

set.seed(1)
res <- router$request("GET", "/dataset/testdata/trace/ab/")
expect_equal(res$status, 200)
expected_warnings <- list("span too small. fewer data values than degrees of freedom.",
Expand Down Expand Up @@ -62,6 +65,7 @@ test_that("can get disgagregated traces", {
local_add_dataset(dat,
"testdataset")
router <- build_routes()
set.seed(1)
res <- router$request("GET", "/dataset/testdataset/trace/ab/",
query = list(disaggregate = "sex"))
expect_equal(res$status, 200)
Expand Down Expand Up @@ -89,6 +93,7 @@ test_that("can get filtered trace", {
local_add_dataset(dat,
"testdataset")
router <- build_routes()
set.seed(1)
res <- router$request("GET", "/dataset/testdataset/trace/ab/",
query = list(filter = "sex:M"))
expect_equal(res$status, 200)
Expand All @@ -109,6 +114,7 @@ test_that("can get trace filtered by multiple variables", {
local_add_dataset(dat,
"testdataset")
router <- build_routes()
set.seed(1)
res <- router$request("GET", "/dataset/testdataset/trace/ab/",
query = list(filter = "sex%3AM%2Bage%3A0-5"))
expect_equal(res$status, 200)
Expand All @@ -129,6 +135,7 @@ test_that("can get disaggregated and filtered traces", {
local_add_dataset(dat,
"testdataset")
router <- build_routes()
set.seed(1)
res <- router$request("GET", "/dataset/testdataset/trace/ab/",
query = list(disaggregate = "age", filter = "sex:M"))
expect_equal(res$status, 200)
Expand Down
3 changes: 2 additions & 1 deletion tests/testthat/test-router.R
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ test_that("GET /datasets", {
local_add_dataset(data.frame(biomarker = "ab", value = 1, day = 1:10),
"anotherdataset")
router <- build_routes()
set.seed(1)
res <- router$request("GET", "/datasets/")
expect_equal(res$status, 200)
body <- jsonlite::fromJSON(res$body)
Expand All @@ -65,6 +66,7 @@ test_that("GET /dataset<name>", {
sex = c("M", "F")),
"testdataset")
router <- build_routes()
set.seed(1)
res <- router$request("GET", "/dataset/testdataset/")
expect_equal(res$status, 200)
body <- jsonlite::fromJSON(res$body)
Expand All @@ -82,7 +84,6 @@ test_that("GET /dataset/<name>/trace/<biomarker>", {
sex = c("M", "F"))
local_add_dataset(dat,
"testdataset")
set.seed(1)
suppressWarnings({
m <- stats::loess(value ~ day, data = dat[dat["biomarker"] == "ab",], span = 0.75)
model <- list(x = 1:9, y = stats::predict(m, tibble::data_frame(day = 1:9)))
Expand Down
116 changes: 116 additions & 0 deletions tests/testthat/test-session.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
test_that("GET /", {
res <- target_get_root()
expect_equal(res, jsonlite::unbox("Welcome to serovizr"))

endpoint <- get_root()
res_endpoint <- endpoint$run()
expect_equal(res_endpoint$status_code, 200)
expect_equal(res_endpoint$content_type, "application/json")
expect_equal(res_endpoint$data, res)

router <- build_routes()
res_api <- router$request("GET", "/")
expect_equal(res_api$status, 200)
expect_equal(res_api$body, res_endpoint$body)
})

test_that("GET /version", {
res <- target_get_version()
expect_equal(res, jsonlite::unbox(as.character(packageVersion("serovizr"))))

router <- build_routes()
res_api <- router$request("GET", "/version/")
expect_equal(res_api$status, 200)
body <- jsonlite::fromJSON(res_api$body)
expect_equal(unclass(res), unclass(body$data))
})

test_that("POST /dataset", {
router <- build_routes()
request <- local_POST_dataset_request(data.frame(biomarker = "ab",
value = 1,
day = 1:10),
"testdataset")
res <- router$call(request)
expect_equal(res$status, 200)
body <- jsonlite::fromJSON(res$body)
expect_equal(body$data, "testdataset")
porcelain:::porcelain_validator("ResponseSuccess",
root = schema_root,
query = NULL)(res$body)
porcelain:::porcelain_validator("UploadResult",
root = schema_root,
query = NULL)(
jsonlite::toJSON(body$data, auto_unbox = TRUE)
)
})

test_that("GET /datasets", {
local_add_dataset(data.frame(biomarker = "ab", value = 1, day = 1:10),
"testdataset")
local_add_dataset(data.frame(biomarker = "ab", value = 1, day = 1:10),
"anotherdataset")
router <- build_routes()
set.seed(1)
res <- router$request("GET", "/datasets/")
expect_equal(res$status, 200)
body <- jsonlite::fromJSON(res$body)
expect_equal(body$data, c("anotherdataset", "testdataset"))
})

test_that("GET /dataset<name>", {
local_add_dataset(data.frame(biomarker = c("ab", "ba"),
value = 1,
day = 1:10,
age = "0-5",
sex = c("M", "F")),
"testdataset")
router <- build_routes()
set.seed(1)
res <- router$request("GET", "/dataset/testdataset/")
expect_equal(res$status, 200)
body <- jsonlite::fromJSON(res$body)
expect_equal(body$data$variables$name, c("age", "sex"))
expect_equal(body$data$variables$levels, list(c("0-5"), c("M", "F")))
expect_equal(body$data$biomarkers, c("ab", "ba"))
expect_equal(body$data$xcol, "day")
})

test_that("GET /dataset/<name>/trace/<biomarker>", {
dat <- data.frame(biomarker = c("ab", "ba"),
value = c(1, 1.5, 2, 3, 3.2, 4, 5, 6, 6.1, 7),
day = 1:10,
age = "0-5",
sex = c("M", "F"))
local_add_dataset(dat,
"testdataset")
suppressWarnings({
m <- stats::loess(value ~ day, data = dat[dat["biomarker"] == "ab",], span = 0.75)
model <- list(x = 1:9, y = stats::predict(m, tibble::data_frame(day = 1:9)))
})
router <- build_routes()
set.seed(1)
res <- router$request("GET", "/dataset/testdataset/trace/ab/")
expect_equal(res$status, 200)
expected_warnings <- list("span too small. fewer data values than degrees of freedom.",
"pseudoinverse used at 0.96",
"neighborhood radius 4.04",
"reciprocal condition number 0",
"There are other near singularities as well. 16.322")
expected <- jsonlite::toJSON(list(list(name = jsonlite::unbox("all"),
model = model,
raw = list(
x = c(1, 3, 5, 7, 9),
y = c(1, 2, 3.2, 5, 6.1)),
warnings = lapply(expected_warnings,
jsonlite::unbox))
))
body <- jsonlite::fromJSON(res$body)
expect_equal(body$data, jsonlite::fromJSON(expected))
})

test_that("requests without trailing slash are redirected", {
router <- build_routes()
res_api <- router$request("GET", "/version")
expect_equal(res_api$status, 307)
})
Loading

0 comments on commit dee2e0b

Please sign in to comment.