| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 1 | offlineConnection <- function() { |
| 2 | methods::new( |
| 3 | "KorAPConnection", |
| 4 | apiUrl = "https://example.invalid/", |
| 5 | KorAPUrl = "https://example.invalid/", |
| 6 | # keeps the unrelated "authorize your application" warning out of the way |
| 7 | authorizationSupported = FALSE, |
| 8 | verbose = FALSE |
| 9 | ) |
| 10 | } |
| 11 | |
| 12 | # Returning no candidates lets collocationAnalysis() finish without contacting |
| 13 | # the server, so that the caching around it can be tested offline. |
| 14 | mockEmptyAnalysis <- function() { |
| 15 | testthat::local_mocked_bindings( |
| 16 | collocatesQuery = function(...) tibble::tibble(), |
| 17 | .package = "RKorAPClient", |
| 18 | .env = parent.frame() |
| 19 | ) |
| 20 | } |
| 21 | |
| 22 | test_that("cache files record the parameters of the analysis", { |
| 23 | mockEmptyAnalysis() |
| 24 | cacheFile <- tempfile(fileext = ".rds") |
| 25 | on.exit(unlink(cacheFile), add = TRUE) |
| 26 | |
| 27 | collocationAnalysis(offlineConnection(), "Test", minOccur = 3, cacheAs = cacheFile) |
| 28 | |
| 29 | stored <- attr(readRDS(cacheFile), RKorAPClient:::collocationCacheAttribute) |
| 30 | expect_false(is.null(stored)) |
| 31 | expect_equal(stored$parameters$node, "Test") |
| 32 | expect_equal(stored$parameters$minOccur, 3) |
| 33 | expect_equal(stored$apiUrl, "https://example.invalid/") |
| 34 | # every parameter of the analysis is recorded, not just those visible in the |
| 35 | # result, so that added parameters are covered automatically |
| 36 | expect_true(all(c( |
| 37 | "topCollocatesLimit", "searchHitsSampleLimit", "exactFrequencies", |
| 38 | "stopwords", "threshold", "collocateFilterRegex", "seed" |
| 39 | ) %in% names(stored$parameters))) |
| 40 | # kco and cacheAs are not parameters of the analysis |
| 41 | expect_false(any(c("kco", "cacheAs") %in% names(stored$parameters))) |
| 42 | }) |
| 43 | |
| 44 | test_that("the returned result is the same whether it was cached or not", { |
| 45 | mockEmptyAnalysis() |
| 46 | cacheFile <- tempfile(fileext = ".rds") |
| 47 | on.exit(unlink(cacheFile), add = TRUE) |
| 48 | kco <- offlineConnection() |
| 49 | |
| 50 | fresh <- collocationAnalysis(kco, "Test", cacheAs = cacheFile) |
| 51 | fromCache <- collocationAnalysis(kco, "Test", cacheAs = cacheFile) |
| 52 | |
| 53 | expect_equal(fromCache, fresh) |
| 54 | # the parameters live in the file only, not in the returned value |
| 55 | expect_null(attr(fromCache, RKorAPClient:::collocationCacheAttribute)) |
| 56 | }) |
| 57 | |
| 58 | test_that("an unchanged call is served from the cache without contacting the server", { |
| 59 | cacheFile <- tempfile(fileext = ".rds") |
| 60 | on.exit(unlink(cacheFile), add = TRUE) |
| 61 | kco <- offlineConnection() |
| 62 | |
| 63 | local({ |
| 64 | mockEmptyAnalysis() |
| 65 | collocationAnalysis(kco, "Test", minOccur = 3, cacheAs = cacheFile) |
| 66 | }) |
| 67 | |
| 68 | testthat::local_mocked_bindings( |
| 69 | collocatesQuery = function(...) stop("server must not be contacted"), |
| 70 | .package = "RKorAPClient" |
| 71 | ) |
| 72 | expect_no_warning(collocationAnalysis(kco, "Test", minOccur = 3, cacheAs = cacheFile)) |
| 73 | }) |
| 74 | |
| 75 | test_that("changed parameters make the analysis be recomputed, with a warning", { |
| 76 | cacheFile <- tempfile(fileext = ".rds") |
| 77 | on.exit(unlink(cacheFile), add = TRUE) |
| 78 | kco <- offlineConnection() |
| 79 | |
| 80 | local({ |
| 81 | mockEmptyAnalysis() |
| 82 | collocationAnalysis(kco, "Test", minOccur = 3, cacheAs = cacheFile) |
| 83 | }) |
| 84 | |
| 85 | testthat::local_mocked_bindings( |
| 86 | collocatesQuery = function(...) stop("recomputed"), |
| 87 | .package = "RKorAPClient" |
| 88 | ) |
| 89 | |
| 90 | # the warning names the parameter that differs, and the analysis is rerun |
| 91 | expect_warning( |
| 92 | expect_error( |
| 93 | collocationAnalysis(kco, "Test", minOccur = 5, cacheAs = cacheFile), |
| 94 | "recomputed" |
| 95 | ), |
| 96 | "minOccur" |
| 97 | ) |
| 98 | |
| 99 | # a changed node is caught as well |
| 100 | expect_warning( |
| 101 | expect_error( |
| 102 | collocationAnalysis(kco, "Other", minOccur = 3, cacheAs = cacheFile), |
| 103 | "recomputed" |
| 104 | ), |
| 105 | "node" |
| 106 | ) |
| 107 | }) |
| 108 | |
| 109 | test_that("a recomputed analysis overwrites the stale cache file", { |
| 110 | cacheFile <- tempfile(fileext = ".rds") |
| 111 | on.exit(unlink(cacheFile), add = TRUE) |
| 112 | kco <- offlineConnection() |
| 113 | |
| 114 | local({ |
| 115 | mockEmptyAnalysis() |
| 116 | collocationAnalysis(kco, "Test", minOccur = 3, cacheAs = cacheFile) |
| 117 | }) |
| 118 | |
| 119 | local({ |
| 120 | mockEmptyAnalysis() |
| 121 | expect_warning( |
| 122 | collocationAnalysis(kco, "Test", minOccur = 5, cacheAs = cacheFile), |
| 123 | "minOccur" |
| 124 | ) |
| 125 | }) |
| 126 | |
| 127 | stored <- attr(readRDS(cacheFile), RKorAPClient:::collocationCacheAttribute) |
| 128 | expect_equal(stored$parameters$minOccur, 5) |
| 129 | }) |
| 130 | |
| 131 | test_that("cache files written without parameters are still used", { |
| 132 | cacheFile <- tempfile(fileext = ".rds") |
| 133 | on.exit(unlink(cacheFile), add = TRUE) |
| 134 | kco <- offlineConnection() |
| 135 | |
| 136 | # as written by RKorAPClient 1.3.0 |
| 137 | legacy <- tibble::tibble(node = "Test", collocate = "c", logDice = 7) |
| 138 | saveRDS(legacy, cacheFile) |
| 139 | |
| 140 | testthat::local_mocked_bindings( |
| 141 | collocatesQuery = function(...) stop("server must not be contacted"), |
| 142 | .package = "RKorAPClient" |
| 143 | ) |
| 144 | expect_equal(collocationAnalysis(kco, "Test", cacheAs = cacheFile), legacy) |
| 145 | }) |
| 146 | |
| 147 | test_that("differingCollocationCacheParameters reports what changed", { |
| 148 | differing <- RKorAPClient:::differingCollocationCacheParameters |
| 149 | |
| 150 | stored <- list( |
| 151 | parameters = list(node = "Test", minOccur = 3, vc = ""), |
| 152 | dots = list(), |
| 153 | apiUrl = "https://korap.ids-mannheim.de/api/v1.0/" |
| 154 | ) |
| 155 | |
| 156 | expect_equal(differing(stored, stored), character(0)) |
| 157 | |
| 158 | changed <- stored |
| 159 | changed$parameters$minOccur <- 5 |
| 160 | expect_equal(differing(stored, changed), "minOccur") |
| 161 | |
| 162 | changed <- stored |
| 163 | changed$parameters$minOccur <- 5 |
| 164 | changed$parameters$vc <- "textType=/Zeit.*/" |
| 165 | expect_setequal(differing(stored, changed), c("minOccur", "vc")) |
| 166 | |
| 167 | changed <- stored |
| 168 | changed$dots <- list(smoothingConstant = 1) |
| 169 | expect_equal(differing(stored, changed), "...") |
| 170 | |
| 171 | changed <- stored |
| 172 | changed$apiUrl <- "https://korap.dnb.de/api/v1.0/" |
| 173 | expect_equal(differing(stored, changed), "KorAP instance") |
| 174 | }) |