| 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 | |
| Marc Kupietz | c902c23 | 2026-09-08 07:58:01 +0200 | [diff] [blame] | 29 | stored <- attr(readRDS(cacheFile), RKorAPClient:::cacheAsAttribute) |
| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 30 | expect_false(is.null(stored)) |
| Marc Kupietz | c902c23 | 2026-09-08 07:58:01 +0200 | [diff] [blame] | 31 | expect_equal(stored$scoreVersion, RKorAPClient:::cacheAsScoreVersion) |
| 32 | expect_equal(stored$packageVersion, as.character(utils::packageVersion("RKorAPClient"))) |
| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 33 | expect_equal(stored$parameters$node, "Test") |
| 34 | expect_equal(stored$parameters$minOccur, 3) |
| 35 | expect_equal(stored$apiUrl, "https://example.invalid/") |
| 36 | # every parameter of the analysis is recorded, not just those visible in the |
| 37 | # result, so that added parameters are covered automatically |
| 38 | expect_true(all(c( |
| 39 | "topCollocatesLimit", "searchHitsSampleLimit", "exactFrequencies", |
| 40 | "stopwords", "threshold", "collocateFilterRegex", "seed" |
| 41 | ) %in% names(stored$parameters))) |
| 42 | # kco and cacheAs are not parameters of the analysis |
| 43 | expect_false(any(c("kco", "cacheAs") %in% names(stored$parameters))) |
| 44 | }) |
| 45 | |
| 46 | test_that("the returned result is the same whether it was cached or not", { |
| 47 | mockEmptyAnalysis() |
| 48 | cacheFile <- tempfile(fileext = ".rds") |
| 49 | on.exit(unlink(cacheFile), add = TRUE) |
| 50 | kco <- offlineConnection() |
| 51 | |
| 52 | fresh <- collocationAnalysis(kco, "Test", cacheAs = cacheFile) |
| 53 | fromCache <- collocationAnalysis(kco, "Test", cacheAs = cacheFile) |
| 54 | |
| 55 | expect_equal(fromCache, fresh) |
| 56 | # the parameters live in the file only, not in the returned value |
| Marc Kupietz | c902c23 | 2026-09-08 07:58:01 +0200 | [diff] [blame] | 57 | expect_null(attr(fromCache, RKorAPClient:::cacheAsAttribute)) |
| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 58 | }) |
| 59 | |
| 60 | test_that("an unchanged call is served from the cache without contacting the server", { |
| 61 | cacheFile <- tempfile(fileext = ".rds") |
| 62 | on.exit(unlink(cacheFile), add = TRUE) |
| 63 | kco <- offlineConnection() |
| 64 | |
| 65 | local({ |
| 66 | mockEmptyAnalysis() |
| 67 | collocationAnalysis(kco, "Test", minOccur = 3, cacheAs = cacheFile) |
| 68 | }) |
| 69 | |
| 70 | testthat::local_mocked_bindings( |
| 71 | collocatesQuery = function(...) stop("server must not be contacted"), |
| 72 | .package = "RKorAPClient" |
| 73 | ) |
| 74 | expect_no_warning(collocationAnalysis(kco, "Test", minOccur = 3, cacheAs = cacheFile)) |
| 75 | }) |
| 76 | |
| 77 | test_that("changed parameters make the analysis be recomputed, with a warning", { |
| 78 | cacheFile <- tempfile(fileext = ".rds") |
| 79 | on.exit(unlink(cacheFile), add = TRUE) |
| 80 | kco <- offlineConnection() |
| 81 | |
| 82 | local({ |
| 83 | mockEmptyAnalysis() |
| 84 | collocationAnalysis(kco, "Test", minOccur = 3, cacheAs = cacheFile) |
| 85 | }) |
| 86 | |
| 87 | testthat::local_mocked_bindings( |
| 88 | collocatesQuery = function(...) stop("recomputed"), |
| 89 | .package = "RKorAPClient" |
| 90 | ) |
| 91 | |
| 92 | # the warning names the parameter that differs, and the analysis is rerun |
| 93 | expect_warning( |
| 94 | expect_error( |
| 95 | collocationAnalysis(kco, "Test", minOccur = 5, cacheAs = cacheFile), |
| 96 | "recomputed" |
| 97 | ), |
| 98 | "minOccur" |
| 99 | ) |
| 100 | |
| 101 | # a changed node is caught as well |
| 102 | expect_warning( |
| 103 | expect_error( |
| 104 | collocationAnalysis(kco, "Other", minOccur = 3, cacheAs = cacheFile), |
| 105 | "recomputed" |
| 106 | ), |
| 107 | "node" |
| 108 | ) |
| 109 | }) |
| 110 | |
| 111 | test_that("a recomputed analysis overwrites the stale cache file", { |
| 112 | cacheFile <- tempfile(fileext = ".rds") |
| 113 | on.exit(unlink(cacheFile), add = TRUE) |
| 114 | kco <- offlineConnection() |
| 115 | |
| 116 | local({ |
| 117 | mockEmptyAnalysis() |
| 118 | collocationAnalysis(kco, "Test", minOccur = 3, cacheAs = cacheFile) |
| 119 | }) |
| 120 | |
| 121 | local({ |
| 122 | mockEmptyAnalysis() |
| 123 | expect_warning( |
| 124 | collocationAnalysis(kco, "Test", minOccur = 5, cacheAs = cacheFile), |
| 125 | "minOccur" |
| 126 | ) |
| 127 | }) |
| 128 | |
| Marc Kupietz | c902c23 | 2026-09-08 07:58:01 +0200 | [diff] [blame] | 129 | stored <- attr(readRDS(cacheFile), RKorAPClient:::cacheAsAttribute) |
| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 130 | expect_equal(stored$parameters$minOccur, 5) |
| 131 | }) |
| 132 | |
| Marc Kupietz | c902c23 | 2026-09-08 07:58:01 +0200 | [diff] [blame] | 133 | test_that("cache files written before the scores were corrected are refused", { |
| 134 | mockEmptyAnalysis() |
| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 135 | cacheFile <- tempfile(fileext = ".rds") |
| 136 | on.exit(unlink(cacheFile), add = TRUE) |
| 137 | kco <- offlineConnection() |
| 138 | |
| Marc Kupietz | c902c23 | 2026-09-08 07:58:01 +0200 | [diff] [blame] | 139 | # as written by RKorAPClient 1.3.0, whose logDice and ll differ from today's |
| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 140 | legacy <- tibble::tibble(node = "Test", collocate = "c", logDice = 7) |
| 141 | saveRDS(legacy, cacheFile) |
| 142 | |
| Marc Kupietz | c902c23 | 2026-09-08 07:58:01 +0200 | [diff] [blame] | 143 | expect_warning( |
| 144 | result <- collocationAnalysis(kco, "Test", cacheAs = cacheFile), |
| 145 | "logDice" |
| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 146 | ) |
| Marc Kupietz | c902c23 | 2026-09-08 07:58:01 +0200 | [diff] [blame] | 147 | expect_false(identical(result, legacy)) |
| 148 | # and the stale file is replaced by one that records what wrote it |
| 149 | expect_false(is.null(attr(readRDS(cacheFile), RKorAPClient:::cacheAsAttribute))) |
| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 150 | }) |
| 151 | |
| Marc Kupietz | c902c23 | 2026-09-08 07:58:01 +0200 | [diff] [blame] | 152 | test_that("cache files of an older version are refused, naming it", { |
| 153 | mockEmptyAnalysis() |
| 154 | cacheFile <- tempfile(fileext = ".rds") |
| 155 | on.exit(unlink(cacheFile), add = TRUE) |
| 156 | kco <- offlineConnection() |
| 157 | |
| 158 | collocationAnalysis(kco, "Test", cacheAs = cacheFile) |
| 159 | aged <- readRDS(cacheFile) |
| 160 | record <- attr(aged, RKorAPClient:::cacheAsAttribute) |
| 161 | record$scoreVersion <- "1.3.0" |
| 162 | record$packageVersion <- "1.3.0" |
| 163 | attr(aged, RKorAPClient:::cacheAsAttribute) <- record |
| 164 | saveRDS(aged, cacheFile) |
| 165 | |
| 166 | expect_warning(collocationAnalysis(kco, "Test", cacheAs = cacheFile), "1\\.3\\.0") |
| 167 | }) |
| 168 | |
| 169 | test_that("cacheAsRejectionReason says why a cache file cannot be used", { |
| 170 | reason <- RKorAPClient:::cacheAsRejectionReason |
| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 171 | |
| 172 | stored <- list( |
| Marc Kupietz | c902c23 | 2026-09-08 07:58:01 +0200 | [diff] [blame] | 173 | scoreVersion = RKorAPClient:::cacheAsScoreVersion, |
| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 174 | parameters = list(node = "Test", minOccur = 3, vc = ""), |
| 175 | dots = list(), |
| 176 | apiUrl = "https://korap.ids-mannheim.de/api/v1.0/" |
| 177 | ) |
| 178 | |
| Marc Kupietz | c902c23 | 2026-09-08 07:58:01 +0200 | [diff] [blame] | 179 | expect_null(reason(stored, stored)) |
| 180 | expect_match(reason(NULL, stored), "before RKorAPClient") |
| 181 | |
| 182 | # a file from before the corrections: an older score generation, and the |
| 183 | # version that wrote it, which the reason names |
| 184 | aged <- stored |
| 185 | aged$scoreVersion <- "1.3.0" |
| 186 | aged$packageVersion <- "1.3.0" |
| 187 | expect_match(reason(aged, stored), "written by RKorAPClient 1\\.3\\.0") |
| 188 | |
| 189 | # one that does not even say what wrote it |
| 190 | anonymous <- stored |
| 191 | anonymous$scoreVersion <- NULL |
| 192 | expect_match(reason(anonymous, stored), "written before RKorAPClient") |
| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 193 | |
| 194 | changed <- stored |
| 195 | changed$parameters$minOccur <- 5 |
| Marc Kupietz | c902c23 | 2026-09-08 07:58:01 +0200 | [diff] [blame] | 196 | expect_match(reason(stored, changed), "minOccur") |
| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 197 | |
| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 198 | changed$parameters$vc <- "textType=/Zeit.*/" |
| Marc Kupietz | c902c23 | 2026-09-08 07:58:01 +0200 | [diff] [blame] | 199 | expect_match(reason(stored, changed), "minOccur, vc") |
| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 200 | |
| 201 | changed <- stored |
| 202 | changed$dots <- list(smoothingConstant = 1) |
| Marc Kupietz | c902c23 | 2026-09-08 07:58:01 +0200 | [diff] [blame] | 203 | expect_match(reason(stored, changed), "\\.\\.\\.") |
| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 204 | |
| 205 | changed <- stored |
| 206 | changed$apiUrl <- "https://korap.dnb.de/api/v1.0/" |
| Marc Kupietz | c902c23 | 2026-09-08 07:58:01 +0200 | [diff] [blame] | 207 | expect_match(reason(stored, changed), "KorAP instance") |
| Marc Kupietz | 37f9607 | 2026-09-03 07:18:11 +0200 | [diff] [blame] | 208 | }) |