From 22014703719897d485b0bb961cc1d75bb0fe5f7f Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Wed, 19 Aug 2026 21:53:21 +0200 Subject: [PATCH 1/2] Improve diagnostics when Codex cross-link index fetch fails Co-authored-by: Cursor --- .../Building/CodexBuildService.cs | 1 + .../Elastic.Documentation.LinkIndex.csproj | 1 + .../GitLinkIndexReader.cs | 37 ++++- .../CrossLinks/CrossLinkFetchDiagnostics.cs | 42 ++++++ .../CrossLinks/CrossLinkFetcher.cs | 11 +- .../CrossLinks/CrossLinkResolver.cs | 3 + .../DocSetConfigurationCrossLinkFetcher.cs | 79 ++++++----- .../IsolatedBuildService.cs | 1 + .../Http/ReloadableGeneratorState.cs | 1 + .../CrossLinks/CrossLinkFetchFailureTests.cs | 126 ++++++++++++++++++ 10 files changed, 260 insertions(+), 42 deletions(-) create mode 100644 src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetchDiagnostics.cs create mode 100644 tests/Elastic.Markdown.Tests/CrossLinks/CrossLinkFetchFailureTests.cs diff --git a/src/Elastic.Codex/Building/CodexBuildService.cs b/src/Elastic.Codex/Building/CodexBuildService.cs index 534683e3a9..5da4f4ca8f 100644 --- a/src/Elastic.Codex/Building/CodexBuildService.cs +++ b/src/Elastic.Codex/Building/CodexBuildService.cs @@ -212,6 +212,7 @@ public async Task BuildAll( buildContext.Configuration, codexLinkIndexReader: buildContext.Configuration.Registry != DocSetRegistry.Public ? codexLinkIndexReader : null); var crossLinks = await fetcher.FetchCrossLinks(ctx); + CrossLinkFetchDiagnostics.EmitFetchFailures(context.Collector, buildContext.ConfigurationPath.FullName, crossLinks); if (crossLinks.CodexRepositories is not null) codexRepos.UnionWith(crossLinks.CodexRepositories); var uriResolver = new CodexAwareUriResolver(codexRepos.ToFrozenSet(), useRelativePaths: true); diff --git a/src/Elastic.Documentation.LinkIndex/Elastic.Documentation.LinkIndex.csproj b/src/Elastic.Documentation.LinkIndex/Elastic.Documentation.LinkIndex.csproj index 64138d5889..415ffdc3ff 100644 --- a/src/Elastic.Documentation.LinkIndex/Elastic.Documentation.LinkIndex.csproj +++ b/src/Elastic.Documentation.LinkIndex/Elastic.Documentation.LinkIndex.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Elastic.Documentation.LinkIndex/GitLinkIndexReader.cs b/src/Elastic.Documentation.LinkIndex/GitLinkIndexReader.cs index 205c6a6432..76284f0c8c 100644 --- a/src/Elastic.Documentation.LinkIndex/GitLinkIndexReader.cs +++ b/src/Elastic.Documentation.LinkIndex/GitLinkIndexReader.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO.Abstractions; +using Elastic.Documentation; using Elastic.Documentation.Configuration; using Elastic.Documentation.FileSystems; using Elastic.Documentation.Links; @@ -24,11 +25,16 @@ public class GitLinkIndexReader : ILinkIndexReader, IDisposable private readonly string _environment; private readonly IFileSystem _fileSystem; + private readonly IEnvironmentVariables _environmentVariables; private readonly bool _skipFetch; private readonly SemaphoreSlim _cloneLock = new(1, 1); private bool _ensuredClone; - public GitLinkIndexReader(string environment, ApplicationDataFileSystem? fileSystem = null, bool skipFetch = false) + public GitLinkIndexReader( + string environment, + ApplicationDataFileSystem? fileSystem = null, + bool skipFetch = false, + IEnvironmentVariables? environmentVariables = null) { if (string.IsNullOrWhiteSpace(environment)) throw new ArgumentException("Environment must be specified in the codex configuration (e.g., 'internal', 'security').", nameof(environment)); @@ -36,6 +42,7 @@ public GitLinkIndexReader(string environment, ApplicationDataFileSystem? fileSys _environment = environment; _fileSystem = fileSystem ?? new ApplicationDataFileSystem(); _skipFetch = skipFetch; + _environmentVariables = environmentVariables ?? SystemEnvironmentVariables.Instance; } /// @@ -123,11 +130,11 @@ private async Task EnsureCloneAsync(Cancel cancellationToken) } } - private static string GetCodexLinkIndexGitUrl() + private string GetCodexLinkIndexGitUrl() { - if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GITHUB_ACTIONS"))) + if (_environmentVariables.IsRunningOnCI) { - var token = Environment.GetEnvironmentVariable("GITHUB_TOKEN"); + var token = _environmentVariables.GetEnvironmentVariable("GITHUB_TOKEN"); return !string.IsNullOrEmpty(token) ? $"https://oauth2:{token}@github.com/{LinkIndexOrigin}.git" : $"https://github.com/{LinkIndexOrigin}.git"; @@ -136,7 +143,7 @@ private static string GetCodexLinkIndexGitUrl() return $"git@github.com:{LinkIndexOrigin}.git"; } - private static void RunGit(string workingDirectory, params string[] args) + private void RunGit(string workingDirectory, params string[] args) { var startInfo = new ProcessStartInfo { @@ -158,6 +165,24 @@ private static void RunGit(string workingDirectory, params string[] args) process.WaitForExit(); if (process.ExitCode != 0) - throw new InvalidOperationException($"Git command failed (exit {process.ExitCode}): {stderr.Trim()}"); + throw new InvalidOperationException(DescribeCloneFailure( + stderr, + _environmentVariables.IsRunningOnCI, + !string.IsNullOrEmpty(_environmentVariables.GetEnvironmentVariable("GITHUB_TOKEN")))); + } + + internal static string DescribeCloneFailure(string gitStderr, bool onActions, bool hasToken) + { + var message = $"Git clone failed: {gitStderr.Trim()}"; + + if (onActions && !hasToken) + return $"{message}{Environment.NewLine}{Environment.NewLine}" + + "GitHub Actions did not provide GITHUB_TOKEN for the private Elastic Internal Docs link index." + + $"{Environment.NewLine}Fork pull_request jobs do not receive the OIDC token needed to fetch this token. Push fork branches to the upstream repository." + + $"{Environment.NewLine}For same-repository jobs, confirm permissions.id-token: write and the catalog-info token policy."; + + return !onActions + ? $"{message}{Environment.NewLine}{Environment.NewLine}Run 'docs-builder codex clone' first, or ensure SSH access to github.com works for git@github.com:elastic/codex-link-index.git." + : message; } } diff --git a/src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetchDiagnostics.cs b/src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetchDiagnostics.cs new file mode 100644 index 0000000000..74d636eb0f --- /dev/null +++ b/src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetchDiagnostics.cs @@ -0,0 +1,42 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using Elastic.Documentation.Configuration.Builder; +using Elastic.Documentation.Diagnostics; + +namespace Elastic.Documentation.Links.CrossLinks; + +public static class CrossLinkFetchDiagnostics +{ + private const string CodexLinkIndexUrl = "https://github.com/elastic/codex-link-index"; + + public static void EmitFetchFailures(IDiagnosticsCollector collector, string configurationPath, FetchedCrossLinks crossLinks) + { + if (crossLinks.FetchFailures.Count == 0) + return; + + collector.EmitError(configurationPath, FormatSummary(crossLinks)); + } + + internal static string FormatSummary(FetchedCrossLinks crossLinks) + { + var fetchFailures = crossLinks.FetchFailures; + var repositories = fetchFailures.Keys.Order(StringComparer.Ordinal).ToArray(); + var isCodexFailure = repositories.All(repository => + crossLinks.RegistryByRepository?.GetValueOrDefault(repository) is { } registry + && registry != DocSetRegistry.Public); + var distinctReasons = fetchFailures.Values.Distinct(StringComparer.Ordinal).ToArray(); + var heading = isCodexFailure && distinctReasons.Length == 1 + ? $"Could not fetch the Elastic Internal Docs link index from {CodexLinkIndexUrl}." + : $"Could not fetch cross-link index data for: {string.Join(", ", repositories)}."; + var details = distinctReasons.Length == 1 + ? distinctReasons[0] + : string.Join(Environment.NewLine, repositories.Select(repository => $"{repository}: {fetchFailures[repository]}")); + var validation = repositories.Length == 1 + ? $"Cross-links to {repositories[0]} were not validated." + : $"Cross-links to these repositories were not validated: {string.Join(", ", repositories)}."; + + return $"{heading}{Environment.NewLine}{Environment.NewLine}{details}{Environment.NewLine}{Environment.NewLine}{validation}"; + } +} diff --git a/src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetcher.cs b/src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetcher.cs index 1baf0e7562..f5ba25af99 100644 --- a/src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetcher.cs +++ b/src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetcher.cs @@ -46,7 +46,13 @@ public record FetchedCrossLinks /// True when all declared repositories resolved without falling back to placeholder data. /// When false, callers should avoid caching so a subsequent reload retries the fetch. /// - public bool IsComplete { get; init; } = true; + public bool IsComplete => FetchFailures.Count == 0; + + /// + /// Repositories whose link index could not be fetched, mapped to a human-readable reason. + /// Callers emit one summary diagnostic and suppress per-link path errors for these repositories. + /// + public FrozenDictionary FetchFailures { get; init; } = FrozenDictionary.Empty; public static FetchedCrossLinks Empty { get; } = new() { @@ -55,7 +61,8 @@ public record FetchedCrossLinks LinkIndexEntries = new Dictionary().ToFrozenDictionary(), RegistryUrlsByRepository = null, RegistryByRepository = null, - CodexRepositories = null + CodexRepositories = null, + FetchFailures = FrozenDictionary.Empty }; } diff --git a/src/Elastic.Documentation.Links/CrossLinks/CrossLinkResolver.cs b/src/Elastic.Documentation.Links/CrossLinks/CrossLinkResolver.cs index d329393b40..d58ac0741a 100644 --- a/src/Elastic.Documentation.Links/CrossLinks/CrossLinkResolver.cs +++ b/src/Elastic.Documentation.Links/CrossLinks/CrossLinkResolver.cs @@ -75,6 +75,9 @@ public static bool TryResolve( { resolvedUri = null; + if (fetchedCrossLinks.FetchFailures.ContainsKey(crossLinkUri.Scheme)) + return false; + // First, check if the repository is in the declared repositories list, even if it's not in the link references var isDeclaredRepo = fetchedCrossLinks.DeclaredRepositories.Contains(crossLinkUri.Scheme); diff --git a/src/Elastic.Documentation.Links/CrossLinks/DocSetConfigurationCrossLinkFetcher.cs b/src/Elastic.Documentation.Links/CrossLinks/DocSetConfigurationCrossLinkFetcher.cs index ec8ab8aa46..af37655676 100644 --- a/src/Elastic.Documentation.Links/CrossLinks/DocSetConfigurationCrossLinkFetcher.cs +++ b/src/Elastic.Documentation.Links/CrossLinks/DocSetConfigurationCrossLinkFetcher.cs @@ -28,6 +28,7 @@ public override async Task FetchCrossLinks(Cancel ctx) var linkIndexEntries = new Dictionary(); var registryUrlsByRepository = new Dictionary(); var registryByRepository = new Dictionary(); + var fetchFailures = new Dictionary(); var codexRepositories = new HashSet(); var declaredRepositories = new HashSet(); @@ -35,9 +36,11 @@ public override async Task FetchCrossLinks(Cancel ctx) var useDualRegistry = configuration.Registry != DocSetRegistry.Public && _codexReader is not null; // Fetch each registry once up front so per-repository lookups don't trigger N S3 round-trips. - var publicRegistry = await TryGetRegistry(publicReader, ctx); - var codexRegistry = useDualRegistry ? await TryGetRegistry(_codexReader!, ctx) : null; - var hadFetchFailures = false; + var (publicRegistry, publicRegistryFailure) = await TryGetRegistry(publicReader, ctx); + LinkRegistry? codexRegistry = null; + string? codexRegistryFailure = null; + if (useDualRegistry) + (codexRegistry, codexRegistryFailure) = await TryGetRegistry(_codexReader!, ctx); foreach (var entry in configuration.CrossLinkEntries) { @@ -46,44 +49,52 @@ public override async Task FetchCrossLinks(Cancel ctx) var isCodexEntry = useDualRegistry && entry.Registry != DocSetRegistry.Public; var reader = isCodexEntry ? _codexReader! : publicReader; var registry = isCodexEntry ? codexRegistry : publicRegistry; + var registryFailure = isCodexEntry ? codexRegistryFailure : publicRegistryFailure; + registryUrlsByRepository[entry.Repository] = reader.RegistryUrl; if (isCodexEntry) _ = codexRepositories.Add(entry.Repository); - try + if (registry is null) { - if (registry is null || !registry.Repositories.TryGetValue(entry.Repository, out var repoBranches)) - throw new Exception($"Repository {entry.Repository} not found in link index"); + fetchFailures[entry.Repository] = + registryFailure ?? $"Failed to fetch link index registry from {reader.RegistryUrl}"; + } + else + { + try + { + if (!registry.Repositories.TryGetValue(entry.Repository, out var repoBranches)) + throw new Exception($"Repository {entry.Repository} not found in link index"); - var linkIndexEntry = GetNextContentSourceLinkIndexEntry(repoBranches, entry.Repository); - var linkReference = await FetchLinkIndexEntryFromReader(reader, entry.Repository, linkIndexEntry, ctx); + var linkIndexEntry = GetNextContentSourceLinkIndexEntry(repoBranches, entry.Repository); + var linkReference = await FetchLinkIndexEntryFromReader(reader, entry.Repository, linkIndexEntry, ctx); - linkReferences.Add(entry.Repository, linkReference); - linkIndexEntries.Add(entry.Repository, linkIndexEntry); - registryUrlsByRepository[entry.Repository] = reader.RegistryUrl; + linkReferences.Add(entry.Repository, linkReference); + linkIndexEntries.Add(entry.Repository, linkIndexEntry); + } + catch (Exception ex) + { + fetchFailures[entry.Repository] = ex.Message; + _logger.LogWarning(ex, "Error fetching link data for repository '{Repository}'. Cross-links to this repository may not resolve correctly.", entry.Repository); + } } - catch (Exception ex) - { - hadFetchFailures = true; - _logger.LogWarning(ex, "Error fetching link data for repository '{Repository}'. Cross-links to this repository may not resolve correctly.", entry.Repository); - _ = registryUrlsByRepository.TryAdd(entry.Repository, reader.RegistryUrl); - if (!linkReferences.ContainsKey(entry.Repository)) + if (!linkReferences.ContainsKey(entry.Repository)) + { + linkReferences.Add(entry.Repository, new RepositoryLinks { - linkReferences.Add(entry.Repository, new RepositoryLinks + Links = [], + Origin = new GitCheckoutInformation { - Links = [], - Origin = new GitCheckoutInformation - { - Branch = "main", - RepositoryName = entry.Repository, - Remote = "origin", - Ref = "refs/heads/main" - }, - UrlPathPrefix = "", - CrossLinks = [] - }); - } + Branch = "main", + RepositoryName = entry.Repository, + Remote = "origin", + Ref = "refs/heads/main" + }, + UrlPathPrefix = "", + CrossLinks = [] + }); } } @@ -95,15 +106,15 @@ public override async Task FetchCrossLinks(Cancel ctx) RegistryUrlsByRepository = registryUrlsByRepository.ToFrozenDictionary(), RegistryByRepository = registryByRepository.ToFrozenDictionary(), CodexRepositories = codexRepositories.Count > 0 ? codexRepositories.ToFrozenSet() : null, - IsComplete = !hadFetchFailures, + FetchFailures = fetchFailures.ToFrozenDictionary(), }; } - private async Task TryGetRegistry(ILinkIndexReader reader, Cancel ctx) + private async Task<(LinkRegistry? Registry, string? FailureReason)> TryGetRegistry(ILinkIndexReader reader, Cancel ctx) { try { - return await reader.GetRegistry(ctx); + return (await reader.GetRegistry(ctx), null); } catch (OperationCanceledException) { @@ -112,7 +123,7 @@ public override async Task FetchCrossLinks(Cancel ctx) catch (Exception ex) { _logger.LogWarning(ex, "Failed to fetch link index registry from {RegistryUrl}", reader.RegistryUrl); - return null; + return (null, ex.Message); } } } diff --git a/src/services/Elastic.Documentation.Isolated/IsolatedBuildService.cs b/src/services/Elastic.Documentation.Isolated/IsolatedBuildService.cs index b492828af7..720124f820 100644 --- a/src/services/Elastic.Documentation.Isolated/IsolatedBuildService.cs +++ b/src/services/Elastic.Documentation.Isolated/IsolatedBuildService.cs @@ -139,6 +139,7 @@ public async Task Build( context.Configuration, codexLinkIndexReader: codexReader); var crossLinks = await crossLinkFetcher.FetchCrossLinks(ctx); + CrossLinkFetchDiagnostics.EmitFetchFailures(context.Collector, context.ConfigurationPath.FullName, crossLinks); IUriEnvironmentResolver? uriResolver = crossLinks.CodexRepositories is not null ? new CodexAwareUriResolver(crossLinks.CodexRepositories) : null; diff --git a/src/tooling/docs-builder/Http/ReloadableGeneratorState.cs b/src/tooling/docs-builder/Http/ReloadableGeneratorState.cs index 7e667d6600..969fac4d53 100644 --- a/src/tooling/docs-builder/Http/ReloadableGeneratorState.cs +++ b/src/tooling/docs-builder/Http/ReloadableGeneratorState.cs @@ -89,6 +89,7 @@ public async Task ReloadAsync(Cancel ctx, bool reloadConfiguration = true) if (crossLinks is null || reloadConfiguration) { crossLinks = await _crossLinkFetcher.FetchCrossLinks(ctx); + CrossLinkFetchDiagnostics.EmitFetchFailures(_context.Collector, _context.ConfigurationPath.FullName, crossLinks); // Only cache successful fetches so transient failures get retried on the next reload. _cachedCrossLinks = crossLinks.IsComplete ? crossLinks : null; } diff --git a/tests/Elastic.Markdown.Tests/CrossLinks/CrossLinkFetchFailureTests.cs b/tests/Elastic.Markdown.Tests/CrossLinks/CrossLinkFetchFailureTests.cs new file mode 100644 index 0000000000..623589b538 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/CrossLinks/CrossLinkFetchFailureTests.cs @@ -0,0 +1,126 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Collections.Frozen; +using AwesomeAssertions; +using Elastic.Documentation; +using Elastic.Documentation.Configuration; +using Elastic.Documentation.Configuration.Builder; +using Elastic.Documentation.LinkIndex; +using Elastic.Documentation.Links; +using Elastic.Documentation.Links.CrossLinks; + +namespace Elastic.Markdown.Tests.CrossLinks; + +public class CrossLinkFetchFailureTests(ITestOutputHelper output) +{ + [Fact] + public void TryResolve_WhenFetchFailed_DoesNotEmitPerLinkError() + { + var crossLinks = BuildCrossLinksWithFetchFailure("synthetics-service", "Git clone failed: auth error"); + + string? emittedError = null; + var resolver = new IsolatedBuildEnvironmentUriResolver(); + var success = CrossLinkResolver.TryResolve( + s => emittedError = s, + crossLinks, + resolver, + new Uri("synthetics-service://index.md", UriKind.Absolute), + out _ + ); + + success.Should().BeFalse(); + emittedError.Should().BeNull(); + } + + [Fact] + public void EmitFetchFailures_EmitsOneSummaryForTheConfigurationFile() + { + var collector = new TestDiagnosticsCollector(output); + var crossLinks = BuildCrossLinksWithFetchFailure( + "synthetics-service", + "Git clone failed: fatal: could not read Username for 'https://github.com'"); + + CrossLinkFetchDiagnostics.EmitFetchFailures(collector, "/docs/docset.yml", crossLinks); + + var diagnostic = collector.Diagnostics.Should().ContainSingle().Which; + diagnostic.File.Should().Be("/docs/docset.yml"); + diagnostic.Message.Should().Contain("Could not fetch the Elastic Internal Docs link index from https://github.com/elastic/codex-link-index"); + diagnostic.Message.Should().Contain("Git clone failed:"); + diagnostic.Message.Should().Contain("Cross-links to synthetics-service were not validated."); + diagnostic.Message.Should().NotContain("is not a valid link"); + } + + private static FetchedCrossLinks BuildCrossLinksWithFetchFailure(string repository, string failureReason) + { + var emptyRepositoryLinks = new RepositoryLinks + { + Links = [], + Origin = new GitCheckoutInformation + { + Branch = "main", + RepositoryName = repository, + Remote = "origin", + Ref = "refs/heads/main" + }, + UrlPathPrefix = "", + CrossLinks = [] + }; + + return new FetchedCrossLinks + { + DeclaredRepositories = [repository], + LinkReferences = new Dictionary { [repository] = emptyRepositoryLinks }.ToFrozenDictionary(), + LinkIndexEntries = new Dictionary().ToFrozenDictionary(), + RegistryUrlsByRepository = new Dictionary + { + [repository] = "https://github.com/elastic/codex-link-index" + }.ToFrozenDictionary(), + RegistryByRepository = new Dictionary + { + [repository] = DocSetRegistry.Internal + }.ToFrozenDictionary(), + FetchFailures = new Dictionary { [repository] = failureReason }.ToFrozenDictionary() + }; + } +} + +public class GitLinkIndexReaderDescribeCloneFailureTests +{ + [Fact] + public void ActionsWithoutToken_UsernameError_MentionsForkPullRequest() + { + var message = GitLinkIndexReader.DescribeCloneFailure( + "fatal: could not read Username for 'https://github.com': No such device or address", + onActions: true, + hasToken: false); + + message.Should().Contain("Git clone failed:"); + message.Should().Contain("GitHub Actions did not provide GITHUB_TOKEN"); + message.Should().Contain("Fork pull_request jobs do not receive the OIDC token"); + message.Should().Contain("Push fork branches to the upstream repository"); + message.Should().Contain("permissions.id-token: write"); + } + + [Fact] + public void ActionsWithToken_DoesNotAddForkHint() + { + var message = GitLinkIndexReader.DescribeCloneFailure("fatal: remote error", onActions: true, hasToken: true); + + message.Should().Be("Git clone failed: fatal: remote error"); + } + + [Fact] + public void LocalEnvironment_MentionsCodexCloneOrSsh() + { + var message = GitLinkIndexReader.DescribeCloneFailure( + "Permission denied (publickey).", + onActions: false, + hasToken: false); + + message.Should().Contain("Git clone failed:"); + message.Should().Contain("docs-builder codex clone"); + message.Should().Contain("git@github.com:elastic/codex-link-index.git"); + } +} From a9fa14686f593542a09936fa5d6e8ff968a60f18 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Wed, 19 Aug 2026 22:00:55 +0200 Subject: [PATCH 2/2] Drop InternalsVisibleTo from LinkIndex to Markdown tests Tests of fetch-failure diagnostics already go through public APIs. Co-authored-by: Cursor --- .../Elastic.Documentation.LinkIndex.csproj | 1 - .../GitLinkIndexReader.cs | 2 +- .../CrossLinks/CrossLinkFetchDiagnostics.cs | 2 +- .../CrossLinks/CrossLinkFetchFailureTests.cs | 41 ------------------- 4 files changed, 2 insertions(+), 44 deletions(-) diff --git a/src/Elastic.Documentation.LinkIndex/Elastic.Documentation.LinkIndex.csproj b/src/Elastic.Documentation.LinkIndex/Elastic.Documentation.LinkIndex.csproj index 415ffdc3ff..64138d5889 100644 --- a/src/Elastic.Documentation.LinkIndex/Elastic.Documentation.LinkIndex.csproj +++ b/src/Elastic.Documentation.LinkIndex/Elastic.Documentation.LinkIndex.csproj @@ -15,7 +15,6 @@ - diff --git a/src/Elastic.Documentation.LinkIndex/GitLinkIndexReader.cs b/src/Elastic.Documentation.LinkIndex/GitLinkIndexReader.cs index 76284f0c8c..db05a3bfcf 100644 --- a/src/Elastic.Documentation.LinkIndex/GitLinkIndexReader.cs +++ b/src/Elastic.Documentation.LinkIndex/GitLinkIndexReader.cs @@ -171,7 +171,7 @@ private void RunGit(string workingDirectory, params string[] args) !string.IsNullOrEmpty(_environmentVariables.GetEnvironmentVariable("GITHUB_TOKEN")))); } - internal static string DescribeCloneFailure(string gitStderr, bool onActions, bool hasToken) + private static string DescribeCloneFailure(string gitStderr, bool onActions, bool hasToken) { var message = $"Git clone failed: {gitStderr.Trim()}"; diff --git a/src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetchDiagnostics.cs b/src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetchDiagnostics.cs index 74d636eb0f..e360bb9bf4 100644 --- a/src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetchDiagnostics.cs +++ b/src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetchDiagnostics.cs @@ -19,7 +19,7 @@ public static void EmitFetchFailures(IDiagnosticsCollector collector, string con collector.EmitError(configurationPath, FormatSummary(crossLinks)); } - internal static string FormatSummary(FetchedCrossLinks crossLinks) + private static string FormatSummary(FetchedCrossLinks crossLinks) { var fetchFailures = crossLinks.FetchFailures; var repositories = fetchFailures.Keys.Order(StringComparer.Ordinal).ToArray(); diff --git a/tests/Elastic.Markdown.Tests/CrossLinks/CrossLinkFetchFailureTests.cs b/tests/Elastic.Markdown.Tests/CrossLinks/CrossLinkFetchFailureTests.cs index 623589b538..a808a29b83 100644 --- a/tests/Elastic.Markdown.Tests/CrossLinks/CrossLinkFetchFailureTests.cs +++ b/tests/Elastic.Markdown.Tests/CrossLinks/CrossLinkFetchFailureTests.cs @@ -5,9 +5,7 @@ using System.Collections.Frozen; using AwesomeAssertions; using Elastic.Documentation; -using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Builder; -using Elastic.Documentation.LinkIndex; using Elastic.Documentation.Links; using Elastic.Documentation.Links.CrossLinks; @@ -85,42 +83,3 @@ private static FetchedCrossLinks BuildCrossLinksWithFetchFailure(string reposito }; } } - -public class GitLinkIndexReaderDescribeCloneFailureTests -{ - [Fact] - public void ActionsWithoutToken_UsernameError_MentionsForkPullRequest() - { - var message = GitLinkIndexReader.DescribeCloneFailure( - "fatal: could not read Username for 'https://github.com': No such device or address", - onActions: true, - hasToken: false); - - message.Should().Contain("Git clone failed:"); - message.Should().Contain("GitHub Actions did not provide GITHUB_TOKEN"); - message.Should().Contain("Fork pull_request jobs do not receive the OIDC token"); - message.Should().Contain("Push fork branches to the upstream repository"); - message.Should().Contain("permissions.id-token: write"); - } - - [Fact] - public void ActionsWithToken_DoesNotAddForkHint() - { - var message = GitLinkIndexReader.DescribeCloneFailure("fatal: remote error", onActions: true, hasToken: true); - - message.Should().Be("Git clone failed: fatal: remote error"); - } - - [Fact] - public void LocalEnvironment_MentionsCodexCloneOrSsh() - { - var message = GitLinkIndexReader.DescribeCloneFailure( - "Permission denied (publickey).", - onActions: false, - hasToken: false); - - message.Should().Contain("Git clone failed:"); - message.Should().Contain("docs-builder codex clone"); - message.Should().Contain("git@github.com:elastic/codex-link-index.git"); - } -}