Skip to content

Commit 9eddbb5

Browse files
committed
Workflow fix
1 parent a52591d commit 9eddbb5

3 files changed

Lines changed: 60 additions & 5 deletions

File tree

.github/workflows/publish.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,14 @@ jobs:
117117
user: ${{ secrets.NUGET_USER }}
118118

119119
- name: Push packages to NuGet.org
120-
# Push all packages and symbol packages to NuGet.org
121-
# --skip-duplicate ensures idempotency - already published versions are skipped
122-
# The API key is obtained from the login step's output (valid for ~1 hour)
120+
# Push primary packages only. The NuGet client discovers and publishes the
121+
# matching .snupkg from the current package directory. Do not push .snupkg
122+
# files explicitly: with --skip-duplicate, rebuilt symbols can be uploaded
123+
# for an already-published .nupkg and fail NuGet symbol validation.
123124
if: ${{ github.event.inputs.dry_run != 'true' }}
124125
run: |
125-
for package in ./artifacts/*.nupkg ./artifacts/*.snupkg; do
126+
cd ./artifacts
127+
for package in *.nupkg; do
126128
echo "Pushing: $package"
127129
dotnet nuget push "$package" \
128130
--api-key "${{ steps.nuget-login.outputs.NUGET_API_KEY }}" \

src/VastAI.NET/VastAI.NET.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<!-- NuGet Package Properties (common properties inherited from Directory.Build.props) -->
77
<PackageId>VastAI.NET</PackageId>
8-
<Version>1.0.0</Version>
8+
<Version>1.0.1</Version>
99
<Description>Typed .NET client for Vast.ai REST provider operations.</Description>
1010
<PackageTags>alos;vastai;vast;cloud-gpu;rest-client</PackageTags>
1111
</PropertyGroup>

tests/VastAI.NET.Tests/VastApiClientTests.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ namespace VastAI.NET.Tests;
22

33
using System.Net;
44
using System.Text.Json;
5+
using Microsoft.Extensions.Logging;
56
using Microsoft.Extensions.Logging.Abstractions;
67
using Microsoft.Extensions.Options;
78
using Models;
@@ -238,6 +239,26 @@ public void Create_WithMissingRuntimeApiKey_ThrowsConfigurationException()
238239
Assert.Contains("Vast API key is required", exception.Message);
239240
}
240241

242+
/// <summary>Failed Vast responses must not copy the configured bearer token into exceptions or logs.</summary>
243+
[Fact]
244+
public async Task GetInstancesAsync_WhenRequestFails_DoesNotLeakApiKeyInExceptionOrLogs()
245+
{
246+
const string apiKey = "live-secret-value-that-must-not-appear";
247+
var handler = new SequenceHandler(
248+
new HttpResponseMessage(HttpStatusCode.Unauthorized) { Content = new StringContent("""{"error":"unauthorized"}""") });
249+
var logger = new CapturingLogger<VastApiClient>();
250+
var client = new VastApiClient(
251+
new HttpClient(handler),
252+
Options.Create(new VastAIOptions { ApiKey = apiKey, ApiBaseUri = new Uri("https://console.vast.ai/") }),
253+
logger);
254+
255+
var exception = await Assert.ThrowsAsync<Exceptions.VastAIOperationException>(() => client.GetInstancesAsync(
256+
TestContext.Current.CancellationToken));
257+
258+
Assert.DoesNotContain(apiKey, exception.ToString(), StringComparison.Ordinal);
259+
Assert.DoesNotContain(apiKey, string.Join(Environment.NewLine, logger.Messages), StringComparison.Ordinal);
260+
}
261+
241262
/// <summary>Creates a client with a fake handler and deterministic options.</summary>
242263
private static VastApiClient CreateClient(HttpMessageHandler handler)
243264
{
@@ -284,4 +305,36 @@ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage reques
284305
: _responses.Dequeue());
285306
}
286307
}
308+
309+
/// <summary>Captures rendered log messages without writing them to test output.</summary>
310+
private sealed class CapturingLogger<T> : ILogger<T>
311+
{
312+
public List<string> Messages { get; } = [];
313+
314+
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => NullScope.Instance;
315+
316+
public bool IsEnabled(LogLevel logLevel) => true;
317+
318+
public void Log<TState>(
319+
LogLevel logLevel,
320+
EventId eventId,
321+
TState state,
322+
Exception? exception,
323+
Func<TState, Exception?, string> formatter)
324+
{
325+
Messages.Add(formatter(state, exception));
326+
if (exception is not null)
327+
Messages.Add(exception.ToString());
328+
}
329+
}
330+
331+
/// <summary>Reusable no-op logging scope.</summary>
332+
private sealed class NullScope : IDisposable
333+
{
334+
public static readonly NullScope Instance = new();
335+
336+
public void Dispose()
337+
{
338+
}
339+
}
287340
}

0 commit comments

Comments
 (0)