From f36be7f7c190b7351b1b3fa456f433d6826bf9bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Bana=C5=9B?= Date: Wed, 1 Apr 2026 11:24:43 +0000 Subject: [PATCH] Reapply "feat: add per-repo GraphQL fallback before REST for GitLab (#765)" This reverts commit 61a76d0777a7dc1e8490514ff2d40c95dfa7128d. Co-authored-by: Ona --- R/EngineGraphQLGitLab.R | 65 ++++++ R/GQLQueryGitLab.R | 53 +++++ R/GitHostGitLab.R | 107 ++++++++- tests/testthat/_snaps/01-get_repos-GitLab.md | 106 --------- tests/testthat/helper-fixtures-repos.R | 47 ++++ tests/testthat/test-01-get_repos-GitLab.R | 230 ++++++++++++++++++- 6 files changed, 493 insertions(+), 115 deletions(-) delete mode 100644 tests/testthat/_snaps/01-get_repos-GitLab.md diff --git a/R/EngineGraphQLGitLab.R b/R/EngineGraphQLGitLab.R index 651b89f3..78ad8850 100644 --- a/R/EngineGraphQLGitLab.R +++ b/R/EngineGraphQLGitLab.R @@ -216,6 +216,39 @@ EngineGraphQLGitLab <- R6::R6Class( return(full_repos_list) }, + # Per-repo fallback: first list project paths with a minimal + # query, then fetch each repo individually with a lightweight + # query (no languages / issueStatusCounts). + get_repos_from_org_per_repo = function(org, verbose = TRUE) { + full_paths <- private$get_repo_paths_from_org( + org = org, + verbose = verbose + ) + if (inherits(full_paths, "graphql_error")) { + return(full_paths) + } + if (length(full_paths) == 0) { + return(list()) + } + repos_list <- purrr::map(full_paths, function(full_path) { + response <- self$gql_response( + gql_query = self$gql_query$repo_by_fullpath_light(), + vars = list("fullPath" = full_path), + verbose = verbose + ) + if (inherits(response, "graphql_error") || + is.null(response$data$project)) { + if (verbose) { + cli::cli_alert_warning("Failed to fetch repo: {full_path}") + } + return(NULL) + } + list(node = response$data$project) + }) |> + purrr::compact() + return(repos_list) + }, + prepare_repos_table = function(repos_list, org) { if (length(repos_list) > 0) { repos_table <- purrr::map(repos_list, function(repo) { @@ -514,6 +547,38 @@ EngineGraphQLGitLab <- R6::R6Class( } ), private = list( + get_repo_paths_from_org = function(org, verbose = TRUE) { + full_paths <- list() + next_page <- TRUE + repo_cursor <- "" + while (next_page) { + response <- self$gql_response( + gql_query = self$gql_query$repos_by_org_minimal(), + vars = list( + "org" = org, + "repo_cursor" = repo_cursor + ), + verbose = verbose + ) + if (inherits(response, "graphql_error")) { + return(response) + } + core_response <- response$data$group$projects + paths <- purrr::map_chr( + core_response$edges, + ~ .$node$fullPath + ) + next_page <- core_response$pageInfo$hasNextPage + if (is.null(next_page)) next_page <- FALSE + if (length(paths) == 0) next_page <- FALSE + if (next_page) { + repo_cursor <- core_response$pageInfo$endCursor + } + full_paths <- c(full_paths, as.list(paths)) + } + return(unlist(full_paths)) + }, + get_repos_page = function(org = NULL, projects_ids = NULL, type = "organization", diff --git a/R/GQLQueryGitLab.R b/R/GQLQueryGitLab.R index 02c7b194..cd1d1956 100644 --- a/R/GQLQueryGitLab.R +++ b/R/GQLQueryGitLab.R @@ -73,6 +73,29 @@ GQLQueryGitLab <- R6::R6Class("GQLQueryGitLab", }') }, + # Minimal query to list project paths in a group without + # expensive fields (languages, issues). Used as the first step + # of the per-repo fallback when repos_by_org fails. + repos_by_org_minimal = function() { + paste0(' + query GetRepoPathsByOrg($org: ID! $repo_cursor: String!) { + group(fullPath: $org) { + projects(first: 100 after: $repo_cursor) { + count + pageInfo { + hasNextPage + endCursor + } + edges { + node { + fullPath + } + } + } + } + }') + }, + repo_by_fullpath = function() { paste0(' query GetRepoByFullPath($fullPath: ID!) { @@ -82,6 +105,36 @@ GQLQueryGitLab <- R6::R6Class("GQLQueryGitLab", }') }, + # Lightweight per-repo query that omits languages and + # issueStatusCounts to avoid complexity limits. + repo_by_fullpath_light = function() { + ' + query GetRepoByFullPathLight($fullPath: ID!) { + project(fullPath: $fullPath) { + repo_id: id + repo_name: name + repo_path: path + repo_fullpath: fullPath + ... on Project { + repository { + rootRef + lastCommit { + sha + } + } + } + stars: starCount + forks: forksCount + created_at: createdAt + last_activity_at: lastActivityAt + namespace { + path: fullPath + } + repo_url: webUrl + } + }' + }, + issues_from_repo = function(issues_cursor = "") { paste0(' query getIssuesFromRepo ($fullPath: ID!) { diff --git a/R/GitHostGitLab.R b/R/GitHostGitLab.R index 438a4908..21f6b0d3 100644 --- a/R/GitHostGitLab.R +++ b/R/GitHostGitLab.R @@ -333,16 +333,27 @@ GitHostGitLab <- R6::R6Class("GitHostGitLab", engine_used <- graphql_engine if (inherits(repos_from_org, "graphql_error")) { if (verbose) { - cli::cli_alert_info("Switching to REST API...") + cli::cli_alert_info("Switching to per-repo GraphQL queries...") } - rest_engine <- private$engines$rest - repos_from_org <- rest_engine$get_repos_from_org( - org = url_encode(org), - output = "raw", + repos_from_org <- graphql_engine$get_repos_from_org_per_repo( + org = url_decode(org), verbose = verbose ) - engine_used <- rest_engine - } else { + if (inherits(repos_from_org, "graphql_error")) { + if (verbose) { + cli::cli_alert_info("Switching to REST API...") + } + rest_engine <- private$engines$rest + repos_from_org <- rest_engine$get_repos_from_org( + org = url_encode(org), + output = "raw", + verbose = verbose + ) + engine_used <- rest_engine + } + } + if (!inherits(repos_from_org, "graphql_error") && + engine_used == graphql_engine) { repos_from_org <- purrr::map(repos_from_org, function(repos_data) { repos_data$path <- repos_data$node$repo_path repos_data @@ -565,6 +576,88 @@ GitHostGitLab <- R6::R6Class("GitHostGitLab", return(repos_table) }, + # Override parent to add a per-repo GraphQL fallback before REST. + # Fallback order: repos_by_org query -> per-repo GraphQL -> REST. + get_repos_from_orgs = function(add_languages, verbose, progress) { + if (any(c("all", "org") %in% private$searching_scope)) { + graphql_engine <- private$engines$graphql + gitstats_map(private$orgs, function(org) { + owner_type <- attr(org, "type") %||% "organization" + if (!private$scan_all && verbose) { + show_message( + host = private$host_name, + engine = "graphql", + scope = url_decode(org), + information = paste0("Pulling repositories ", cli_icons$repo) + ) + } + repos_from_org <- graphql_engine$get_repos_from_org( + org = url_decode(org), + owner_type = owner_type, + verbose = verbose + ) + if (!inherits(repos_from_org, "graphql_error")) { + if (length(repos_from_org) > 0) { + repos_table <- repos_from_org |> + graphql_engine$prepare_repos_table( + org = unclass(url_decode(org)) + ) |> + dplyr::filter(organization == unclass(url_decode(org))) + } else { + repos_table <- NULL + } + } else { + if (verbose) { + cli::cli_alert_info("Switching to per-repo GraphQL queries...") + show_message( + host = private$host_name, + engine = "graphql", + scope = url_decode(org), + information = paste0("Pulling repositories per-repo ", cli_icons$repo) + ) + } + repos_from_org <- graphql_engine$get_repos_from_org_per_repo( + org = url_decode(org), + verbose = verbose + ) + if (!inherits(repos_from_org, "graphql_error")) { + if (length(repos_from_org) > 0) { + repos_table <- repos_from_org |> + graphql_engine$prepare_repos_table( + org = unclass(url_decode(org)) + ) |> + dplyr::filter(organization == unclass(url_decode(org))) + } else { + repos_table <- NULL + } + } else { + if (verbose) { + cli::cli_alert_info("Switching to REST API") + show_message( + host = private$host_name, + engine = "rest", + scope = org, + information = paste0("Pulling repositories ", cli_icons$repo) + ) + } + rest_engine <- private$engines$rest + repos_table <- rest_engine$get_repos_from_org( + org = org, + add_languages = add_languages, + output = "full_table", + verbose = verbose + ) |> + rest_engine$prepare_repos_table( + org = org + ) + } + } + return(repos_table) + }, .progress = set_progress_bar(progress, private)) |> + purrr::list_rbind() + } + }, + # Override parent to query repos directly by fullpath instead of # fetching all repos from org/user and filtering client-side. # The parent's approach is slow for GitLab because the repos_by_user diff --git a/tests/testthat/_snaps/01-get_repos-GitLab.md b/tests/testthat/_snaps/01-get_repos-GitLab.md deleted file mode 100644 index 048b78df..00000000 --- a/tests/testthat/_snaps/01-get_repos-GitLab.md +++ /dev/null @@ -1,106 +0,0 @@ -# repos queries are built properly - - Code - gl_repos_by_org_query - Output - [1] "\n query GetReposByOrg($org: ID! $repo_cursor: String!) {\n group(fullPath: $org) {\n projects(first: 100 after: $repo_cursor) {\n \n count\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n node {\n repo_id: id\n repo_name: name\n repo_path: path\n repo_fullpath: fullPath\n ... on Project {\n repository {\n rootRef\n lastCommit {\n sha\n }\n }\n }\n stars: starCount\n forks: forksCount\n created_at: createdAt\n last_activity_at: lastActivityAt\n languages {\n name\n }\n issues: issueStatusCounts {\n all\n closed\n opened\n }\n namespace {\n path: fullPath\n }\n repo_url: webUrl\n }\n }\n }\n }\n }" - ---- - - Code - gl_repos_query - Output - [1] "\n query GetRepo($projects_ids: [ID!]!) {\n projects(ids: $projects_ids first:100) {\n \n count\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n node {\n repo_id: id\n repo_name: name\n repo_path: path\n repo_fullpath: fullPath\n ... on Project {\n repository {\n rootRef\n lastCommit {\n sha\n }\n }\n }\n stars: starCount\n forks: forksCount\n created_at: createdAt\n last_activity_at: lastActivityAt\n languages {\n name\n }\n issues: issueStatusCounts {\n all\n closed\n opened\n }\n namespace {\n path: fullPath\n }\n repo_url: webUrl\n }\n }\n }\n }" - -# repo_by_fullpath query is built properly - - Code - gl_repo_by_fullpath_query - Output - [1] "\n query GetRepoByFullPath($fullPath: ID!) {\n project(fullPath: $fullPath) {\n \n repo_id: id\n repo_name: name\n repo_path: path\n repo_fullpath: fullPath\n ... on Project {\n repository {\n rootRef\n lastCommit {\n sha\n }\n }\n }\n stars: starCount\n forks: forksCount\n created_at: createdAt\n last_activity_at: lastActivityAt\n languages {\n name\n }\n issues: issueStatusCounts {\n all\n closed\n opened\n }\n namespace {\n path: fullPath\n }\n repo_url: webUrl\n }\n }" - -# error handler prints proper messages - - Code - output <- test_graphql_gitlab_priv$handle_graphql_error(responses_list = test_mocker$ - use("repos_graphql_error"), verbose = TRUE) - Message - x GraphQL returned errors: - i Your GraphQL does not recognize [count and languages] fields. - ! Check version of your GitLab. - -# `search_for_code()` works - - Code - gl_search_repos_by_code <- test_rest_gitlab$search_for_code(code = "test", - filename = "TESTFILE", verbose = TRUE, page_max = 2) - Message - > Searching for code [test]... - -# `search_for_code()` handles the 1000 response limit - - Code - gl_search_repos_by_code <- test_rest_gitlab$search_for_code(code = "test", - filename = "TESTFILE", verbose = TRUE, page_max = 2) - Message - > Searching for code [test]... - ! Reached 10 thousand response limit. - -# `search_repos_for_code()` works - - Code - gl_search_repos_by_code <- test_rest_gitlab$search_repos_for_code(code = "test", - repos = "TestRepo", filename = "TESTFILE", verbose = TRUE, page_max = 2) - Message - > Searching for code [test]... - -# get_repos_from_org prints proper message - - Code - gl_repos_from_orgs <- gitlab_testhost_priv$get_repos_from_orgs(add_languages = TRUE, - verbose = TRUE, progress = FALSE) - Message - > [Host:GitLab][Engine:GraphQl][Scope:mbtests] Pulling repositories 🌐... - -# GitLab Host prints message when turning to REST engine (from orgs) - - Code - gl_repos_from_orgs <- gitlab_testhost_priv$get_repos_from_orgs(add_languages = TRUE, - verbose = TRUE, progress = FALSE) - Message - > [Host:GitLab][Engine:GraphQl][Scope:mbtests] Pulling repositories 🌐... - i Switching to REST API - > [Host:GitLab][Engine:REST][Scope:mbtests] Pulling repositories 🌐... - -# `get_repos_from_repos()` prints proper message - - Code - gl_repos_from_repos <- gitlab_testhost_priv$get_repos_from_repos(add_languages = TRUE, - verbose = TRUE, progress = FALSE) - Message - > [Host:GitLab][Engine:GraphQl][Scope:mbtests/gitstatstesting] Pulling repositories 🌐... - -# `get_repos_data` uses cached data from org - - Code - gl_repos_data <- gitlab_testhost_priv$get_repos_data(org = "mbtests", verbose = TRUE) - Message - > Using cached repositories data... - -# get_repos_data prints message when turns to REST engine - - Code - gl_repos_data <- gitlab_testhost_priv$get_repos_data(org = "test_org", verbose = TRUE) - Message - > [test_org] Pulling repositories 🌐 data... - i Switching to REST API... - > Caching repositories for [test_org]... - -# `fill_repos_commit_sha()` fills missing commit_sha via REST - - Code - repos_commit_sha <- gitlab_testhost_fill$fill_repos_commit_sha(repos_table, - verbose = TRUE) - Message - > [Host:GitLab][Engine:REST] Fetching missing commit SHAs for 1 repo... - diff --git a/tests/testthat/helper-fixtures-repos.R b/tests/testthat/helper-fixtures-repos.R index c131ae7e..d3978714 100644 --- a/tests/testthat/helper-fixtures-repos.R +++ b/tests/testthat/helper-fixtures-repos.R @@ -393,3 +393,50 @@ test_fixtures$github_contributors_response <- list( test_fixtures$github_contributor_response, test_fixtures$github_contributor_response ) + +# Minimal repos_by_org response (only fullPath, used by per-repo fallback) +test_fixtures$gitlab_repos_by_org_minimal_response <- list( + "data" = list( + "group" = list( + "projects" = list( + "count" = 2, + "pageInfo" = list( + "hasNextPage" = FALSE, + "endCursor" = "xyz" + ), + "edges" = list( + list("node" = list("fullPath" = "mbtests/gitstatstesting")), + list("node" = list("fullPath" = "mbtests/gitstats-testing-2")) + ) + ) + ) + ) +) + +# Light per-repo response (no languages, no issueStatusCounts) +gitlab_project_node_light <- list( + "repo_id" = "gid://gitlab/Project/61399846", + "repo_name" = "gitstatstesting", + "repo_path" = "gitstatstesting", + "repo_fullpath" = "mbtests/gitstatstesting", + "repository" = list( + "rootRef" = "main", + "lastCommit" = list( + "sha" = "1a2bc3d4e5" + ) + ), + "stars" = 8, + "forks" = 3, + "created_at" = "2023-09-18T00:00:00Z", + "last_activity_at" = "2024-09-18T00:00:00Z", + "namespace" = list( + "path" = "mbtests" + ), + "repo_url" = "https://gitlab.com/mbtests/gitstatstesting" +) + +test_fixtures$gitlab_repo_by_fullpath_light_response <- list( + "data" = list( + "project" = gitlab_project_node_light + ) +) diff --git a/tests/testthat/test-01-get_repos-GitLab.R b/tests/testthat/test-01-get_repos-GitLab.R index abc9a601..b5cbc3e4 100644 --- a/tests/testthat/test-01-get_repos-GitLab.R +++ b/tests/testthat/test-01-get_repos-GitLab.R @@ -279,6 +279,128 @@ test_that("get_repos_from_org handles properly a GraphQL query error", { test_mocker$cache(gitlab_repos_error) }) +test_that("repos_by_org_minimal query is built properly", { + gl_repos_by_org_minimal_query <- + test_gqlquery_gl$repos_by_org_minimal() + expect_snapshot( + gl_repos_by_org_minimal_query + ) +}) + +test_that("repo_by_fullpath_light query is built properly", { + gl_repo_by_fullpath_light_query <- + test_gqlquery_gl$repo_by_fullpath_light() + expect_snapshot( + gl_repo_by_fullpath_light_query + ) +}) + +test_that("`get_repos_from_org_per_repo()` fetches repos individually", { + mockery::stub( + test_graphql_gitlab$get_repos_from_org_per_repo, + "private$get_repo_paths_from_org", + c("mbtests/gitstatstesting", "mbtests/gitstats-testing-2") + ) + mockery::stub( + test_graphql_gitlab$get_repos_from_org_per_repo, + "self$gql_response", + test_fixtures$gitlab_repo_by_fullpath_light_response + ) + repos_list <- test_graphql_gitlab$get_repos_from_org_per_repo( + org = "mbtests", + verbose = FALSE + ) + expect_type(repos_list, "list") + expect_length(repos_list, 2) + expect_true("node" %in% names(repos_list[[1]])) + expect_equal(repos_list[[1]]$node$repo_path, "gitstatstesting") +}) + +test_that("`get_repos_from_org_per_repo()` returns graphql_error when minimal query fails", { + mockery::stub( + test_graphql_gitlab$get_repos_from_org_per_repo, + "private$get_repo_paths_from_org", + test_mocker$use("gitlab_repos_error") + ) + result <- test_graphql_gitlab$get_repos_from_org_per_repo( + org = "mbtests", + verbose = FALSE + ) + expect_s3_class(result, "graphql_error") +}) + +test_that("`get_repos_from_org_per_repo()` skips repos that fail individually", { + mockery::stub( + test_graphql_gitlab$get_repos_from_org_per_repo, + "private$get_repo_paths_from_org", + c("mbtests/gitstatstesting", "mbtests/broken-repo") + ) + error_response <- list("errors" = list(list("message" = "Internal server error"))) + class(error_response) <- c(class(error_response), "graphql_error") + call_count <- 0L + mockery::stub( + test_graphql_gitlab$get_repos_from_org_per_repo, + "self$gql_response", + function(...) { + call_count <<- call_count + 1L + if (call_count == 1L) { + test_fixtures$gitlab_repo_by_fullpath_light_response + } else { + error_response + } + } + ) + repos_list <- test_graphql_gitlab$get_repos_from_org_per_repo( + org = "mbtests", + verbose = FALSE + ) + expect_type(repos_list, "list") + expect_length(repos_list, 1) +}) + +test_that("`get_repo_paths_from_org()` paginates through minimal query", { + mockery::stub( + test_graphql_gitlab_priv$get_repo_paths_from_org, + "self$gql_response", + test_fixtures$gitlab_repos_by_org_minimal_response + ) + paths <- test_graphql_gitlab_priv$get_repo_paths_from_org( + org = "mbtests", + verbose = FALSE + ) + expect_type(paths, "character") + expect_length(paths, 2) + expect_equal(paths[1], "mbtests/gitstatstesting") + expect_equal(paths[2], "mbtests/gitstats-testing-2") +}) + +test_that("`get_repo_paths_from_org()` returns graphql_error on failure", { + mockery::stub( + test_graphql_gitlab_priv$get_repo_paths_from_org, + "self$gql_response", + test_mocker$use("repos_graphql_error") + ) + result <- test_graphql_gitlab_priv$get_repo_paths_from_org( + org = "mbtests", + verbose = FALSE + ) + expect_s3_class(result, "graphql_error") +}) + +test_that("prepare_repos_table handles repos without languages and issues", { + repos_list_light <- list( + list("node" = gitlab_project_node_light) + ) + repos_table <- test_graphql_gitlab$prepare_repos_table( + repos_list = repos_list_light, + org = "mbtests" + ) + expect_repos_table(repos_table) + expect_equal(repos_table$languages, "") + expect_equal(repos_table$issues_open, 0) + expect_equal(repos_table$issues_closed, 0) +}) + test_that("`get_repos_languages()` works", { repos_list <- test_fixtures$gitlab_repositories_rest_response mockery::stub( @@ -469,12 +591,36 @@ test_that("get_repos_from_org prints proper message", { test_mocker$cache(gl_repos_from_orgs) }) -test_that("GitLab Host turns to REST if GraphQL fails with error (org setup)", { +test_that("GitLab Host tries per-repo GraphQL before REST when org query fails", { mockery::stub( gitlab_testhost_priv$get_repos_from_orgs, "graphql_engine$get_repos_from_org", test_mocker$use("gitlab_repos_error") ) + mockery::stub( + gitlab_testhost_priv$get_repos_from_orgs, + "graphql_engine$get_repos_from_org_per_repo", + test_mocker$use("gl_repos_from_org") + ) + gl_repos_from_orgs <- gitlab_testhost_priv$get_repos_from_orgs( + add_languages = TRUE, + verbose = FALSE, + progress = FALSE + ) + expect_repos_table(gl_repos_from_orgs) +}) + +test_that("GitLab Host turns to REST if both GraphQL methods fail (org setup)", { + mockery::stub( + gitlab_testhost_priv$get_repos_from_orgs, + "graphql_engine$get_repos_from_org", + test_mocker$use("gitlab_repos_error") + ) + mockery::stub( + gitlab_testhost_priv$get_repos_from_orgs, + "graphql_engine$get_repos_from_org_per_repo", + test_mocker$use("gitlab_repos_error") + ) mockery::stub( gitlab_testhost_priv$get_repos_from_orgs, "rest_engine$prepare_repos_table", @@ -526,12 +672,38 @@ test_that("`get_repos_from_repos()` returns NULL when no repos found", { expect_null(gl_repos_from_repos) }) +test_that("GitLab Host prints message when falling back to per-repo GraphQL (from orgs)", { + mockery::stub( + gitlab_testhost_priv$get_repos_from_orgs, + "graphql_engine$get_repos_from_org", + test_mocker$use("gitlab_repos_error") + ) + mockery::stub( + gitlab_testhost_priv$get_repos_from_orgs, + "graphql_engine$get_repos_from_org_per_repo", + test_mocker$use("gl_repos_from_org") + ) + gitlab_testhost_priv$searching_scope <- "org" + expect_snapshot( + gl_repos_from_orgs <- gitlab_testhost_priv$get_repos_from_orgs( + add_languages = TRUE, + verbose = TRUE, + progress = FALSE + ) + ) +}) + test_that("GitLab Host prints message when turning to REST engine (from orgs)", { mockery::stub( gitlab_testhost_priv$get_repos_from_orgs, "graphql_engine$get_repos_from_org", test_mocker$use("gitlab_repos_error") ) + mockery::stub( + gitlab_testhost_priv$get_repos_from_orgs, + "graphql_engine$get_repos_from_org_per_repo", + test_mocker$use("gitlab_repos_error") + ) mockery::stub( gitlab_testhost_priv$get_repos_from_orgs, "rest_engine$prepare_repos_table", @@ -709,18 +881,46 @@ test_that("`get_repos_data` pulls data from repos", { expect_gt(length(gl_repos_data[["paths"]]), 0) }) -test_that("get_repos_data turns to REST if GraphQL fails with error", { +test_that("get_repos_data tries per-repo GraphQL before REST", { + mockery::stub( + gitlab_testhost_priv$get_repos_data, + "graphql_engine$get_repos_from_org", + test_mocker$use("gitlab_repos_error") + ) + mockery::stub( + gitlab_testhost_priv$get_repos_data, + "graphql_engine$get_repos_from_org_per_repo", + test_mocker$use("gl_repos_from_org") + ) + gitlab_testhost_priv$searching_scope <- "org" + gitlab_testhost_priv$cached_repos <- list() + gl_repos_data <- gitlab_testhost_priv$get_repos_data( + org = "test_org", + verbose = FALSE + ) + expect_type(gl_repos_data, "list") + expect_type(gl_repos_data[["paths"]], "character") + expect_gt(length(gl_repos_data[["paths"]]), 0) +}) + +test_that("get_repos_data turns to REST if both GraphQL methods fail", { mockery::stub( gitlab_testhost_priv$get_repos_data, "graphql_engine$get_repos_from_org", test_mocker$use("gitlab_repos_error") ) + mockery::stub( + gitlab_testhost_priv$get_repos_data, + "graphql_engine$get_repos_from_org_per_repo", + test_mocker$use("gitlab_repos_error") + ) mockery::stub( gitlab_testhost_priv$get_repos_data, "rest_engine$get_repos_from_org", test_mocker$use("gitlab_rest_repos_from_org_raw") ) gitlab_testhost_priv$searching_scope <- "org" + gitlab_testhost_priv$cached_repos <- list() gl_repos_data <- gitlab_testhost_priv$get_repos_data( org = "test_org", verbose = FALSE @@ -730,12 +930,38 @@ test_that("get_repos_data turns to REST if GraphQL fails with error", { expect_gt(length(gl_repos_data[["paths"]]), 0) }) +test_that("get_repos_data prints message when falling back to per-repo GraphQL", { + mockery::stub( + gitlab_testhost_priv$get_repos_data, + "graphql_engine$get_repos_from_org", + test_mocker$use("gitlab_repos_error") + ) + mockery::stub( + gitlab_testhost_priv$get_repos_data, + "graphql_engine$get_repos_from_org_per_repo", + test_mocker$use("gl_repos_from_org") + ) + gitlab_testhost_priv$searching_scope <- "org" + gitlab_testhost_priv$cached_repos <- list() + expect_snapshot( + gl_repos_data <- gitlab_testhost_priv$get_repos_data( + org = "test_org", + verbose = TRUE + ) + ) +}) + test_that("get_repos_data prints message when turns to REST engine", { mockery::stub( gitlab_testhost_priv$get_repos_data, "graphql_engine$get_repos_from_org", test_mocker$use("gitlab_repos_error") ) + mockery::stub( + gitlab_testhost_priv$get_repos_data, + "graphql_engine$get_repos_from_org_per_repo", + test_mocker$use("gitlab_repos_error") + ) mockery::stub( gitlab_testhost_priv$get_repos_data, "rest_engine$get_repos_from_org",