Skip to content

Commit 908f8fb

Browse files
feat: Support credential type parameter for NLog.
1 parent 25cb750 commit 908f8fb

File tree

10 files changed

+216
-168
lines changed

10 files changed

+216
-168
lines changed

apis/Google.Cloud.Logging.NLog/Google.Cloud.Logging.NLog.IntegrationTests/CredentialTest.cs

Lines changed: 0 additions & 107 deletions
This file was deleted.

apis/Google.Cloud.Logging.NLog/Google.Cloud.Logging.NLog.IntegrationTests/Google.Cloud.Logging.NLog.IntegrationTests.csproj

Lines changed: 0 additions & 21 deletions
This file was deleted.

apis/Google.Cloud.Logging.NLog/Google.Cloud.Logging.NLog.IntegrationTests/NLogFixture.cs

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Copyright 2025 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Newtonsoft.Json;
16+
using NLog;
17+
using System;
18+
using System.IO;
19+
using Xunit;
20+
21+
namespace Google.Cloud.Logging.NLog.Tests
22+
{
23+
/// <summary>
24+
/// Validates the credential resolution process in the NLog configuration.
25+
/// Confirms that the system throws appropriate exceptions when misconfigured.
26+
/// </summary>
27+
/// <remarks>
28+
/// These tests do not cover Google Application Default Credentials (ADC).
29+
/// Because ADC resolution depends on the 'GOOGLE_APPLICATION_CREDENTIALS' environment
30+
/// variable—which cannot be securely modified in our test environment.
31+
/// </remarks>
32+
public class CredentialTests
33+
{
34+
private const string FakeServiceAccountCredential = @"{
35+
""private_key_id"": ""PRIVATE_KEY_ID"",
36+
""private_key"": ""-----BEGIN PRIVATE KEY-----
37+
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJJM6HT4s6btOsfe
38+
2x4zrzrwSUtmtR37XTTi0sPARTDF8uzmXy8UnE5RcVJzEH5T2Ssz/ylX4Sl/CI4L
39+
no1l8j9GiHJb49LSRjWe4Yx936q0Xj9H0R1HTxvjUPqwAsTwy2fKBTog+q1frqc9
40+
o8s2r6LYivUGDVbhuUzCaMJsf+x3AgMBAAECgYEAi0FTXsu/zRswAUGaViQiHjrL
41+
uU65BSHXNVjV/2fLNEKnGWGqpli68z1IXY+S2nwbUak7rnGsq9/0F6jtsW+hZbLk
42+
KXUOuuExpeC5Kd6ngWX/f2jqmhlUabiQijU9cVk7pMq8EHkRtvlosnMTUAEzempu
43+
QUPwn1PZHhmJkBvZ4lECQQDCErrxl+e3BwUDcS0yVEEmCNSG6xdXs2878b8rzbe7
44+
3Mmi6SuuOLi3PU92J+j+f/MOdtYrk13mEDdYmd5dhrt5AkEAwPvDEsDT/W4y4h5n
45+
gv1awGBA5aLFE1JNWM/Gwn4D1cGpEDHKFREaBtxMDCASpHJuw8r7zUywpKhmBZcf
46+
GS37bwJANdSAKfbafLfjuhqwUJ9yGpykZm/a36aTmerp/bpn1iHdg+RtCzwMcDb/
47+
TWSwibbvsflgWmHbz657y4WSWhq+8QJAWrpCNN/ZCk2zuGDo80lfUBAwkoVat8G6
48+
wWU1oZyS+vzIGef+hLb8kHsjeZPej9eIwZ39kcBbT54oELrCkRjwGwJAQ8V2A7lT
49+
ZUp8AsbVqF6rbLiiUfJMo2btGclQu4DEVyS+ymFA65tXDLUuR9EDqJYdqHNZJ5B8
50+
4Z5p2prkjWTLcA\u003d\u003d
51+
-----END PRIVATE KEY-----"",
52+
""client_email"": ""CLIENT_EMAIL"",
53+
""client_id"": ""CLIENT_ID"",
54+
""project_id"": ""PROJECT_ID"",
55+
""type"": ""service_account""}";
56+
57+
private const string FakeUserCredential = @"{
58+
""client_id"": ""CLIENT_ID"",
59+
""client_secret"": ""CLIENT_SECRET"",
60+
""refresh_token"": ""REFRESH_TOKEN"",
61+
""type"": ""authorized_user""}";
62+
63+
[Fact]
64+
public void LoadCredentialFromFile_Succeeds()
65+
{
66+
using var tempCredFile = new TempFile(FakeServiceAccountCredential);
67+
InitializeNLog(GetXml($"credentialFile='{tempCredFile.Path}'"));
68+
}
69+
70+
[Fact]
71+
public void LoadCredentialFromJson_Succeeds() => InitializeNLog(GetXml($"credentialJson='{FakeServiceAccountCredential}'"));
72+
73+
[Fact]
74+
public void LoadCredentialFromFile_BadFormat_Fails()
75+
{
76+
using var tempCredFile = new TempFile("bad json");
77+
var exception = Record.Exception(() => InitializeNLog(GetXml($"credentialFile='{tempCredFile.Path}'")));
78+
Assert.IsType<InvalidOperationException>(exception);
79+
Assert.IsType<JsonReaderException>(exception.InnerException);
80+
}
81+
82+
[Fact]
83+
public void LoadCredentialFromJson_BadFormat_Fails()
84+
{
85+
var exception = Record.Exception(() => InitializeNLog(GetXml("credentialJson='terrible json'")));
86+
Assert.IsType<InvalidOperationException>(exception);
87+
Assert.IsType<JsonReaderException>(exception.InnerException);
88+
}
89+
90+
[Fact]
91+
public void LoadCredentialFromFile_FileDoesNotExist_Fails() =>
92+
Assert.Throws<FileNotFoundException>(() => InitializeNLog(GetXml("credentialFile='non-existent-file'")));
93+
94+
95+
[Fact]
96+
public void LoadCredential_MultipleCredentials_Fails()
97+
{
98+
var exception = Assert.Throws<InvalidOperationException>(() => InitializeNLog(GetXml("credentialFile='some-file' credentialJson='{}'")));
99+
Assert.IsType<InvalidOperationException>(exception);
100+
Assert.Contains("CredentialFile", exception.Message);
101+
Assert.Contains("CredentialJson", exception.Message);
102+
}
103+
104+
[Fact]
105+
public void LoadUserCredentialFromJson_WithCredentialType_Succeeds() =>
106+
InitializeNLog(GetXml($"credentialJson='{FakeUserCredential}' credentialType='authorized_user'"));
107+
108+
[Fact]
109+
public void LoadUserCredentialFromJson_WithWrongCredentialType_Fails() =>
110+
Assert.Throws<InvalidOperationException>(() => InitializeNLog(GetXml($"credentialJson='{FakeServiceAccountCredential}' credentialType='authorized_user'")));
111+
112+
private static string GetXml(string attributes) =>
113+
"<nlog xmlns='http://www.nlog-project.org/schemas/NLog.xsd' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" +
114+
"<extensions><add assembly='Google.Cloud.Logging.NLog'/></extensions>" +
115+
"<targets>" +
116+
$"<target name='stackdriver' xsi:type='GoogleStackdriver' projectId='test-project-id' {attributes} />" +
117+
"</targets>" +
118+
"</nlog>";
119+
120+
private static void InitializeNLog(string config)
121+
{
122+
using var tempConfigFile = new TempFile(config);
123+
using var logFactory = new LogFactory();
124+
logFactory.ThrowExceptions = true;
125+
logFactory.Setup().LoadConfigurationFromFile(tempConfigFile.Path);
126+
Assert.NotNull(logFactory.Configuration);
127+
}
128+
129+
private sealed class TempFile : IDisposable
130+
{
131+
public string Path { get; }
132+
133+
public TempFile(string content = null)
134+
{
135+
Path = System.IO.Path.GetTempFileName();
136+
if (content != null)
137+
{
138+
File.WriteAllText(Path, content);
139+
}
140+
}
141+
142+
public void Dispose()
143+
{
144+
if(File.Exists(Path))
145+
{
146+
File.Delete(Path);
147+
}
148+
}
149+
}
150+
}
151+
}

apis/Google.Cloud.Logging.NLog/Google.Cloud.Logging.NLog.Tests/GoogleStackdriverTargetTest.cs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
using Newtonsoft.Json.Linq;
2020
using NLog;
2121
using NLog.Common;
22-
using NLog.Config;
2322
using NLog.Layouts;
2423
using NLog.Targets;
2524
using NLog.Targets.Wrappers;
@@ -29,6 +28,7 @@
2928
using System.Collections.Concurrent;
3029
using System.Collections.Generic;
3130
using System.Diagnostics;
31+
using System.IO;
3232
using System.Linq;
3333
using System.Reflection;
3434
using System.Runtime.CompilerServices;
@@ -56,6 +56,19 @@ private void LogInfo(IEnumerable<string> messages)
5656
private const string s_projectId = "projectId";
5757
private const string s_logId = "logId";
5858

59+
private const string FakeServiceAccountCredentialJson = @"{
60+
""type"": ""service_account"",
61+
""project_id"": ""test-project"",
62+
""private_key_id"": ""a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"",
63+
""private_key"": ""-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"",
64+
""client_email"": ""test-service-account@test-project.iam.gserviceaccount.com"",
65+
""client_id"": ""123456789012"",
66+
""auth_uri"": ""https://accounts.google.com/o/oauth2/auth"",
67+
""token_uri"": ""https://oauth2.googleapis.com/token"",
68+
""auth_provider_x509_cert_url"": ""https://www.googleapis.com/oauth2/v1/certs"",
69+
""client_x509_cert_url"": ""https://www.googleapis.com/robot/v1/metadata/x509/test-service-account%40test-project.iam.gserviceaccount.com""
70+
}";
71+
5972
private async Task RunTest(
6073
Func<IEnumerable<LogEntry>, Task<WriteLogEntriesResponse>> handlerFn,
6174
Func<GoogleStackdriverTarget, Task> testFn,
@@ -152,6 +165,41 @@ public async Task InvalidOptions_MultipleCredentials()
152165
Assert.True(innerEx.Message.Contains("CredentialFile") && innerEx.Message.Contains("CredentialJson"));
153166
}
154167

168+
[Fact]
169+
public async Task GoogleStackdriverTarget_FromFile_InvalidType_ThrowsArgumentException()
170+
{
171+
var tempFile = Path.GetTempFileName();
172+
File.WriteAllText(tempFile, FakeServiceAccountCredentialJson);
173+
try
174+
{
175+
var target = new GoogleStackdriverTarget
176+
{
177+
ProjectId = "a_project_id",
178+
CredentialFile = Layout.FromString(tempFile),
179+
CredentialType = Layout.FromString("invalid_type"),
180+
};
181+
var nlogEx = await Assert.ThrowsAsync<NLogRuntimeException>(() => ActivateTargetAsync(target));
182+
Assert.IsType<InvalidOperationException>(nlogEx.InnerException);
183+
}
184+
finally
185+
{
186+
File.Delete(tempFile);
187+
}
188+
}
189+
190+
[Fact]
191+
public async Task GoogleStackdriverTarget_FromJson_InvalidType_ThrowsArgumentException()
192+
{
193+
var target = new GoogleStackdriverTarget
194+
{
195+
ProjectId = "a_project_id",
196+
CredentialJson = Layout.FromString(FakeServiceAccountCredentialJson),
197+
CredentialType = Layout.FromString("invalid_type"),
198+
};
199+
var nlogEx = await Assert.ThrowsAsync<NLogRuntimeException>(() => ActivateTargetAsync(target));
200+
Assert.IsType<InvalidOperationException>(nlogEx.InnerException);
201+
}
202+
155203
[Fact]
156204
public async Task InvalidOptions_MultipleJsonConverters()
157205
{

0 commit comments

Comments
 (0)