Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright 2025 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Newtonsoft.Json;
using NLog;
using System;
using System.IO;
using Xunit;

namespace Google.Cloud.Logging.NLog.Tests
{
/// <summary>
/// Validates the credential resolution process in the NLog configuration.
/// Confirms that the system throws appropriate exceptions when misconfigured.
/// </summary>
/// <remarks>
/// These tests do not cover Google Application Default Credentials (ADC).
/// Because ADC resolution depends on the 'GOOGLE_APPLICATION_CREDENTIALS' environment
/// variable—which cannot be securely modified in our test environment.
/// </remarks>
public class CredentialTests
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the corresponding integration tests which validate these cases and moved theme here. On top of this I took the opportunity to test things from the level a config xml is specified and added a couple more test cases.

{
private const string FakeServiceAccountCredential = @"{
""private_key_id"": ""PRIVATE_KEY_ID"",
""private_key"": ""-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJJM6HT4s6btOsfe
2x4zrzrwSUtmtR37XTTi0sPARTDF8uzmXy8UnE5RcVJzEH5T2Ssz/ylX4Sl/CI4L
no1l8j9GiHJb49LSRjWe4Yx936q0Xj9H0R1HTxvjUPqwAsTwy2fKBTog+q1frqc9
o8s2r6LYivUGDVbhuUzCaMJsf+x3AgMBAAECgYEAi0FTXsu/zRswAUGaViQiHjrL
uU65BSHXNVjV/2fLNEKnGWGqpli68z1IXY+S2nwbUak7rnGsq9/0F6jtsW+hZbLk
KXUOuuExpeC5Kd6ngWX/f2jqmhlUabiQijU9cVk7pMq8EHkRtvlosnMTUAEzempu
QUPwn1PZHhmJkBvZ4lECQQDCErrxl+e3BwUDcS0yVEEmCNSG6xdXs2878b8rzbe7
3Mmi6SuuOLi3PU92J+j+f/MOdtYrk13mEDdYmd5dhrt5AkEAwPvDEsDT/W4y4h5n
gv1awGBA5aLFE1JNWM/Gwn4D1cGpEDHKFREaBtxMDCASpHJuw8r7zUywpKhmBZcf
GS37bwJANdSAKfbafLfjuhqwUJ9yGpykZm/a36aTmerp/bpn1iHdg+RtCzwMcDb/
TWSwibbvsflgWmHbz657y4WSWhq+8QJAWrpCNN/ZCk2zuGDo80lfUBAwkoVat8G6
wWU1oZyS+vzIGef+hLb8kHsjeZPej9eIwZ39kcBbT54oELrCkRjwGwJAQ8V2A7lT
ZUp8AsbVqF6rbLiiUfJMo2btGclQu4DEVyS+ymFA65tXDLUuR9EDqJYdqHNZJ5B8
4Z5p2prkjWTLcA\u003d\u003d
-----END PRIVATE KEY-----"",
""client_email"": ""CLIENT_EMAIL"",
""client_id"": ""CLIENT_ID"",
""project_id"": ""PROJECT_ID"",
""type"": ""service_account""}";

private const string FakeUserCredential = @"{
""client_id"": ""CLIENT_ID"",
""client_secret"": ""CLIENT_SECRET"",
""refresh_token"": ""REFRESH_TOKEN"",
""type"": ""authorized_user""}";

[Fact]
public void LoadCredentialFromFile_Succeeds()
{
using var tempCredFile = new TempFile(FakeServiceAccountCredential);
InitializeNLog(GetXml($"credentialFile='{tempCredFile.Path}'"));
}

[Fact]
public void LoadCredentialFromJson_Succeeds() => InitializeNLog(GetXml($"credentialJson='{FakeServiceAccountCredential}'"));

[Fact]
public void LoadCredentialFromFile_BadFormat_Fails()
{
using var tempCredFile = new TempFile("bad json");
var exception = Record.Exception(() => InitializeNLog(GetXml($"credentialFile='{tempCredFile.Path}'")));
Assert.IsType<InvalidOperationException>(exception);
Assert.IsType<JsonReaderException>(exception.InnerException);
}

[Fact]
public void LoadCredentialFromJson_BadFormat_Fails()
{
var exception = Record.Exception(() => InitializeNLog(GetXml("credentialJson='terrible json'")));
Assert.IsType<InvalidOperationException>(exception);
Assert.IsType<JsonReaderException>(exception.InnerException);
}

[Fact]
public void LoadCredentialFromFile_FileDoesNotExist_Fails() =>
Assert.Throws<FileNotFoundException>(() => InitializeNLog(GetXml("credentialFile='non-existent-file'")));


[Fact]
public void LoadCredential_MultipleCredentials_Fails()
{
var exception = Assert.Throws<InvalidOperationException>(() => InitializeNLog(GetXml("credentialFile='some-file' credentialJson='{}'")));
Assert.IsType<InvalidOperationException>(exception);
Assert.Contains("CredentialFile", exception.Message);
Assert.Contains("CredentialJson", exception.Message);
}

[Fact]
public void LoadUserCredentialFromJson_WithCredentialType_Succeeds() =>
InitializeNLog(GetXml($"credentialJson='{FakeUserCredential}' credentialType='authorized_user'"));

[Fact]
public void LoadUserCredentialFromJson_WithWrongCredentialType_Fails() =>
Assert.Throws<InvalidOperationException>(() => InitializeNLog(GetXml($"credentialJson='{FakeServiceAccountCredential}' credentialType='authorized_user'")));

private static string GetXml(string attributes) =>
"<nlog xmlns='http://www.nlog-project.org/schemas/NLog.xsd' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" +
"<extensions><add assembly='Google.Cloud.Logging.NLog'/></extensions>" +
"<targets>" +
$"<target name='stackdriver' xsi:type='GoogleStackdriver' projectId='test-project-id' {attributes} />" +
"</targets>" +
"</nlog>";

private static void InitializeNLog(string config)
{
using var tempConfigFile = new TempFile(config);
using var logFactory = new LogFactory();
logFactory.ThrowExceptions = true;
logFactory.Setup().LoadConfigurationFromFile(tempConfigFile.Path);
Assert.NotNull(logFactory.Configuration);
}

private sealed class TempFile : IDisposable
{
public string Path { get; }

public TempFile(string content = null)
{
Path = System.IO.Path.GetTempFileName();
if (content != null)
{
File.WriteAllText(Path, content);
}
}

public void Dispose()
{
if(File.Exists(Path))
{
File.Delete(Path);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
using Newtonsoft.Json.Linq;
using NLog;
using NLog.Common;
using NLog.Config;
using NLog.Layouts;
using NLog.Targets;
using NLog.Targets.Wrappers;
Expand All @@ -29,6 +28,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
Expand Down Expand Up @@ -56,6 +56,19 @@ private void LogInfo(IEnumerable<string> messages)
private const string s_projectId = "projectId";
private const string s_logId = "logId";

private const string FakeServiceAccountCredentialJson = @"{
""type"": ""service_account"",
""project_id"": ""test-project"",
""private_key_id"": ""a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"",
""private_key"": ""-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"",
""client_email"": ""[email protected]"",
""client_id"": ""123456789012"",
""auth_uri"": ""https://accounts.google.com/o/oauth2/auth"",
""token_uri"": ""https://oauth2.googleapis.com/token"",
""auth_provider_x509_cert_url"": ""https://www.googleapis.com/oauth2/v1/certs"",
""client_x509_cert_url"": ""https://www.googleapis.com/robot/v1/metadata/x509/test-service-account%40test-project.iam.gserviceaccount.com""
}";

private async Task RunTest(
Func<IEnumerable<LogEntry>, Task<WriteLogEntriesResponse>> handlerFn,
Func<GoogleStackdriverTarget, Task> testFn,
Expand Down Expand Up @@ -152,6 +165,41 @@ public async Task InvalidOptions_MultipleCredentials()
Assert.True(innerEx.Message.Contains("CredentialFile") && innerEx.Message.Contains("CredentialJson"));
}

[Fact]
public async Task GoogleStackdriverTarget_FromFile_InvalidType_ThrowsArgumentException()
{
var tempFile = Path.GetTempFileName();
File.WriteAllText(tempFile, FakeServiceAccountCredentialJson);
try
{
var target = new GoogleStackdriverTarget
{
ProjectId = "a_project_id",
CredentialFile = Layout.FromString(tempFile),
CredentialType = Layout.FromString("invalid_type"),
};
var nlogEx = await Assert.ThrowsAsync<NLogRuntimeException>(() => ActivateTargetAsync(target));
Assert.IsType<InvalidOperationException>(nlogEx.InnerException);
}
finally
{
File.Delete(tempFile);
}
}

[Fact]
public async Task GoogleStackdriverTarget_FromJson_InvalidType_ThrowsArgumentException()
{
var target = new GoogleStackdriverTarget
{
ProjectId = "a_project_id",
CredentialJson = Layout.FromString(FakeServiceAccountCredentialJson),
CredentialType = Layout.FromString("invalid_type"),
};
var nlogEx = await Assert.ThrowsAsync<NLogRuntimeException>(() => ActivateTargetAsync(target));
Assert.IsType<InvalidOperationException>(nlogEx.InnerException);
}

[Fact]
public async Task InvalidOptions_MultipleJsonConverters()
{
Expand Down
Loading