Skip to content

Commit 40ff6e5

Browse files
committed
Refactor code generation templates and enhance test coverage.
- Added `CSharpCompiler.AssertCompile` assertions across multiple test files to ensure generated code compiles successfully. - Introduced new test cases for nullable path parameters, query string handling (arrays, dictionaries, deep objects), and OpenAPI specifications. - Refactored path and query parameter handling in Liquid templates for improved readability and correctness. - Generated auto-verified test outputs for new test cases. - Improved handling of constructor parameters in generated clients.
1 parent e4e30bf commit 40ff6e5

File tree

44 files changed

+3433
-252
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+3433
-252
lines changed

src/NSwag.CodeGeneration.CSharp.Tests/AllowNullableBodyParametersTests.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
using Microsoft.AspNetCore.Mvc;
22
using NJsonSchema.NewtonsoftJson.Generation;
33
using NSwag.CodeGeneration.OperationNameGenerators;
4+
using NSwag.CodeGeneration.Tests;
45
using NSwag.Generation.WebApi;
56
using System.ComponentModel.DataAnnotations;
6-
using NSwag.CodeGeneration.Tests;
7+
using System.Reflection;
78

89
namespace NSwag.CodeGeneration.CSharp.Tests
910
{
@@ -46,7 +47,11 @@ public async Task TestNoGuardForOptionalBodyParameter()
4647
{
4748
UseBaseUrl = false,
4849
GenerateClientInterfaces = true,
49-
OperationNameGenerator = new SingleClientFromOperationIdOperationNameGenerator()
50+
OperationNameGenerator = new SingleClientFromOperationIdOperationNameGenerator(),
51+
CSharpGeneratorSettings =
52+
{
53+
Namespace = VerifyHelper.GetNameSpace(),
54+
},
5055
});
5156

5257
var code = codeGen.GenerateFile();
@@ -61,25 +66,47 @@ public async Task TestNullableBodyWithAllowNullableBodyParameters()
6166
{
6267
// Arrange
6368
var generator = await GenerateCode(true);
69+
generator.Settings.CSharpGeneratorSettings.Namespace = VerifyHelper.GetNameSpace();
6470

6571
// Act
6672
var code = generator.GenerateFile();
6773

6874
// Assert
6975
await VerifyHelper.Verify(code);
76+
CSharpCompiler.AssertCompile(code + @"
77+
namespace AllowNullableBodyParametersTests.TestNullableBodyWithAllowNullableBodyParameters
78+
{
79+
public class MyBaseClass
80+
{
81+
public MyBaseClass(MyConfig configuration) {}
82+
}
83+
public class MyConfig {}
84+
}
85+
");
7086
}
7187

7288
[Fact]
7389
public async Task TestNullableBodyWithoutAllowNullableBodyParameters()
7490
{
7591
// Arrange
7692
var generator = await GenerateCode(false);
93+
generator.Settings.CSharpGeneratorSettings.Namespace = VerifyHelper.GetNameSpace();
7794

7895
// Act
7996
var code = generator.GenerateFile();
8097

8198
// Assert
8299
await VerifyHelper.Verify(code);
100+
CSharpCompiler.AssertCompile(code + @"
101+
namespace AllowNullableBodyParametersTests.TestNullableBodyWithoutAllowNullableBodyParameters
102+
{
103+
public class MyBaseClass
104+
{
105+
public MyBaseClass(MyConfig configuration) {}
106+
}
107+
public class MyConfig {}
108+
}
109+
");
83110
}
84111

85112
private static async Task<CSharpClientGenerator> GenerateCode(bool allowNullableBodyParameters)

src/NSwag.CodeGeneration.CSharp.Tests/ArrayParameterTests.cs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,14 @@ public async Task When_parameter_is_array_then_CSharp_is_correct()
6060
var document = await OpenApiDocument.FromJsonAsync(swagger);
6161

6262
// Act
63-
var settings = new CSharpClientGeneratorSettings { ClassName = "MyClass" };
63+
var settings = new CSharpClientGeneratorSettings
64+
{
65+
ClassName = "MyClass",
66+
CSharpGeneratorSettings =
67+
{
68+
Namespace = VerifyHelper.GetNameSpace(),
69+
},
70+
};
6471
var generator = new CSharpClientGenerator(document, settings);
6572
var code = generator.GenerateFile();
6673

@@ -136,7 +143,14 @@ public async Task When_explode_is_false_then_do_not_explode(string explode)
136143
var document = await OpenApiDocument.FromJsonAsync(json);
137144

138145
// Act
139-
var settings = new CSharpClientGeneratorSettings { ClassName = "MyClass" };
146+
var settings = new CSharpClientGeneratorSettings
147+
{
148+
ClassName = "MyClass",
149+
CSharpGeneratorSettings =
150+
{
151+
Namespace = VerifyHelper.GetNameSpace(),
152+
},
153+
};
140154
var generator = new CSharpClientGenerator(document, settings);
141155
var code = generator.GenerateFile();
142156

@@ -214,7 +228,14 @@ public async Task When_explode_is_explicitly_or_implicitly_true_then_explode(str
214228
var document = await OpenApiDocument.FromJsonAsync(json);
215229

216230
// Act
217-
var settings = new CSharpClientGeneratorSettings { ClassName = "MyClass" };
231+
var settings = new CSharpClientGeneratorSettings
232+
{
233+
ClassName = "MyClass",
234+
CSharpGeneratorSettings =
235+
{
236+
Namespace = VerifyHelper.GetNameSpace(),
237+
},
238+
};
218239
var generator = new CSharpClientGenerator(document, settings);
219240
var code = generator.GenerateFile();
220241

@@ -280,7 +301,14 @@ public async Task When_CollectionFormat_is_csv_then_do_not_explode()
280301
var document = await OpenApiDocument.FromJsonAsync(swagger);
281302

282303
// Act
283-
var settings = new CSharpClientGeneratorSettings { ClassName = "MyClass" };
304+
var settings = new CSharpClientGeneratorSettings
305+
{
306+
ClassName = "MyClass",
307+
CSharpGeneratorSettings =
308+
{
309+
Namespace = VerifyHelper.GetNameSpace(),
310+
},
311+
};
284312
var generator = new CSharpClientGenerator(document, settings);
285313
var code = generator.GenerateFile();
286314

@@ -346,7 +374,14 @@ public async Task When_CollectionFormat_is_multi_then_explode()
346374
var document = await OpenApiDocument.FromJsonAsync(swagger);
347375

348376
// Act
349-
var settings = new CSharpClientGeneratorSettings { ClassName = "MyClass" };
377+
var settings = new CSharpClientGeneratorSettings
378+
{
379+
ClassName = "MyClass",
380+
CSharpGeneratorSettings =
381+
{
382+
Namespace = VerifyHelper.GetNameSpace(),
383+
},
384+
};
350385
var generator = new CSharpClientGenerator(document, settings);
351386
var code = generator.GenerateFile();
352387

@@ -421,7 +456,14 @@ public async Task when_content_is_formdata_with_property_array_then_content_shou
421456
var document = await OpenApiDocument.FromJsonAsync(json);
422457

423458
// Act
424-
var settings = new CSharpClientGeneratorSettings { ClassName = "MyClass" };
459+
var settings = new CSharpClientGeneratorSettings
460+
{
461+
ClassName = "MyClass",
462+
CSharpGeneratorSettings =
463+
{
464+
Namespace = VerifyHelper.GetNameSpace(),
465+
},
466+
};
425467
var generator = new CSharpClientGenerator(document, settings);
426468
var code = generator.GenerateFile();
427469

src/NSwag.CodeGeneration.CSharp.Tests/CSharpClientSettingsTests.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ public async Task When_ConfigurationClass_is_set_then_correct_ctor_is_generated(
4848

4949
// Assert
5050
await VerifyHelper.Verify(code);
51+
CSharpCompiler.AssertCompile(code + @"
52+
namespace MyNamespace
53+
{
54+
public class MyBaseClass
55+
{
56+
public MyBaseClass(MyConfig configuration) {}
57+
}
58+
public class MyConfig {}
59+
}
60+
");
5161
}
5262

5363
[Fact]
@@ -72,6 +82,17 @@ public async Task When_UseHttpRequestMessageCreationMethod_is_set_then_CreateReq
7282

7383
// Assert
7484
await VerifyHelper.Verify(code);
85+
CSharpCompiler.AssertCompile(code + @"
86+
namespace MyNamespace
87+
{
88+
public class MyBaseClass
89+
{
90+
public MyBaseClass(MyConfig configuration) {}
91+
protected global::System.Threading.Tasks.Task<global::System.Net.Http.HttpRequestMessage> CreateHttpRequestMessageAsync(global::System.Threading.CancellationToken ct) { return default; }
92+
}
93+
public class MyConfig {}
94+
}
95+
");
7596
}
7697

7798
[Fact]
@@ -162,6 +183,12 @@ public async Task When_custom_http_client_type_is_specified_then_an_instance_of_
162183

163184
// Assert
164185
await VerifyHelper.Verify(code);
186+
CSharpCompiler.AssertCompile(code + @"
187+
namespace CustomNamespace
188+
{
189+
public class CustomHttpClient : global::System.Net.Http.HttpClient { }
190+
}
191+
");
165192
}
166193

167194
[Fact]
@@ -232,6 +259,12 @@ public async Task When_client_base_interface_is_specified_then_client_interface_
232259

233260
// Assert
234261
await VerifyHelper.Verify(code);
262+
CSharpCompiler.AssertCompile(code + @"
263+
namespace MyNamespace
264+
{
265+
public interface IClientBase { }
266+
}
267+
");
235268
}
236269

237270
[Fact]
@@ -256,6 +289,12 @@ public async Task When_client_base_interface_is_specified_with_access_modifier_t
256289

257290
// Assert
258291
await VerifyHelper.Verify(code);
292+
CSharpCompiler.AssertCompile(code + @"
293+
namespace MyNamespace
294+
{
295+
public interface IClientBase { }
296+
}
297+
");
259298
}
260299

261300
[Fact]
@@ -307,6 +346,12 @@ public async Task When_client_interface_generation_is_enabled_and_suppressed_the
307346

308347
// Assert
309348
await VerifyHelper.Verify(code);
349+
CSharpCompiler.AssertCompile(code + @"
350+
namespace MyNamespace
351+
{
352+
public interface IFooClient { }
353+
}
354+
");
310355
}
311356

312357
public class OperationSelectionTestData : IEnumerable<object[]>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
namespace System.Web.Http
2+
{
3+
public class ApiController { }
4+
public abstract class ParameterBindingAttribute : global::System.Attribute
5+
{
6+
public abstract System.Web.Http.Controllers.HttpParameterBinding GetBinding(global::System.Web.Http.Controllers.HttpParameterDescriptor parameter);
7+
}
8+
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
9+
public class HttpGetAttribute : global::System.Attribute { }
10+
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
11+
public class HttpPostAttribute : global::System.Attribute { }
12+
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
13+
public class FromUriAttribute : global::System.Attribute { }
14+
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
15+
public class FromBodyAttribute : global::System.Attribute { }
16+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
17+
public class RouteAttribute : global::System.Attribute
18+
{
19+
public RouteAttribute(string template) { }
20+
}
21+
}
22+
23+
namespace System.Web.Http.Controllers
24+
{
25+
public abstract class HttpParameterBinding
26+
{
27+
protected HttpParameterBinding(global::System.Web.Http.Controllers.HttpParameterDescriptor parameter) { }
28+
public dynamic ActionArguments { get; }
29+
public dynamic Descriptor { get; }
30+
public abstract System.Threading.Tasks.Task ExecuteBindingAsync(global::System.Web.Http.Metadata.ModelMetadataProvider metadataProvider, global::System.Web.Http.Controllers.HttpActionContext actionContext, global::System.Threading.CancellationToken cancellationToken);
31+
}
32+
public abstract class HttpParameterDescriptor
33+
{
34+
public string ParameterName { get; }
35+
public dynamic ActionArguments { get; }
36+
}
37+
public abstract class HttpActionContext
38+
{
39+
public dynamic ActionArguments { get; }
40+
public dynamic Request { get; }
41+
}
42+
}
43+
44+
namespace System.Web.Http.Metadata
45+
{
46+
public abstract class ModelMetadataProvider { }
47+
}

src/NSwag.CodeGeneration.CSharp.Tests/CSharpCompiler.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,27 @@ static CSharpCompiler()
1515
.Append(MetadataReference.CreateFromFile(typeof(HttpClient).Assembly.Location))
1616
.Append(MetadataReference.CreateFromFile(typeof(Microsoft.AspNetCore.Mvc.FileResult).Assembly.Location))
1717
.Append(MetadataReference.CreateFromFile(typeof(Microsoft.AspNetCore.Http.IFormFile).Assembly.Location))
18+
.Append(MetadataReference.CreateFromFile(typeof(Microsoft.AspNetCore.Http.HttpRequest).Assembly.Location))
19+
.Append(MetadataReference.CreateFromFile(typeof(Microsoft.Extensions.Primitives.StringValues).Assembly.Location))
20+
.Append(MetadataReference.CreateFromFile(typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly.Location))
1821
.Append(MetadataReference.CreateFromFile(typeof(Newtonsoft.Json.JsonSerializer).Assembly.Location))
1922
.Append(MetadataReference.CreateFromFile(typeof(System.Net.HttpStatusCode).Assembly.Location))
2023
.Append(MetadataReference.CreateFromFile(typeof(System.ComponentModel.DataAnnotations.RangeAttribute).Assembly.Location))
2124
.Append(MetadataReference.CreateFromFile(typeof(System.Collections.ObjectModel.ObservableCollection<>).Assembly.Location))
2225
.Append(MetadataReference.CreateFromFile(typeof(System.Runtime.Serialization.EnumMemberAttribute).Assembly.Location))
2326
.Append(MetadataReference.CreateFromFile(typeof(System.Text.Json.Serialization.JsonConverter).Assembly.Location))
27+
// stubs
28+
.Append(MetadataReference.CreateFromFile(typeof(System.Web.Http.ApiController).Assembly.Location))
29+
.Append(MetadataReference.CreateFromFile(typeof(System.Web.Http.FromBodyAttribute).Assembly.Location))
30+
.Append(MetadataReference.CreateFromFile(typeof(System.Web.Http.FromUriAttribute).Assembly.Location))
31+
.Append(MetadataReference.CreateFromFile(typeof(System.Web.Http.HttpGetAttribute).Assembly.Location))
32+
.Append(MetadataReference.CreateFromFile(typeof(System.Web.Http.HttpPostAttribute).Assembly.Location))
33+
.Append(MetadataReference.CreateFromFile(typeof(System.Web.Http.ParameterBindingAttribute).Assembly.Location))
34+
.Append(MetadataReference.CreateFromFile(typeof(System.Web.Http.RouteAttribute).Assembly.Location))
35+
.Append(MetadataReference.CreateFromFile(typeof(System.Web.Http.Controllers.HttpActionContext).Assembly.Location))
36+
.Append(MetadataReference.CreateFromFile(typeof(System.Web.Http.Controllers.HttpParameterBinding).Assembly.Location))
37+
.Append(MetadataReference.CreateFromFile(typeof(System.Web.Http.Controllers.HttpParameterDescriptor).Assembly.Location))
38+
.Append(MetadataReference.CreateFromFile(typeof(System.Web.Http.Metadata.ModelMetadataProvider).Assembly.Location))
2439
.ToList();
2540
}
2641

src/NSwag.CodeGeneration.CSharp.Tests/ClientGenerationTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,21 @@ public class ClientGenerationTests
88
[Fact]
99
public async Task CanGenerateFromJiraOpenApiSpecification()
1010
{
11+
// Jira's OpenAPI spec generates code like this:
12+
//// public bool ShowDaysInColumn { get; set; } = MyNamespace.bool.False;
1113
await VerifyOutput("JIRA_OpenAPI", "jira-open-api.json", compile: false);
1214
}
1315

1416
[Fact]
1517
public async Task CanGenerateFromShipBobOpenApiSpecification()
1618
{
17-
await VerifyOutput("ShipBob_OpenAPI", "shipbob-2025-07.json");
19+
await VerifyOutput("ShipBob_OpenAPI", "shipbob-2025-07.json", compile: true);
1820
}
1921

2022
[Fact]
2123
public async Task CanGenerateFromNhsSpineServicesOpenApiSpecification()
2224
{
23-
await VerifyOutput("NHS_SpineServices_OpenAPI", "nhs-spineservices.json");
25+
await VerifyOutput("NHS_SpineServices_OpenAPI", "nhs-spineservices.json", compile: true);
2426
}
2527

2628
private static async Task VerifyOutput(string name, string fileName, bool compile = true)

0 commit comments

Comments
 (0)