Conversation
Replaces switch on JsonTokenType with a recursive method using JsonElement.ValueKind for more robust and accurate type inference. This improves handling of nested objects and arrays, and unifies the logic for converting JSON values to .NET types.
Simplifies and standardizes the deserialization of model properties from dictionaries, removing special handling for JsonElement and streamlining array and primitive type conversions. This improves code readability and maintainability in generated model classes.
Updated the From method in the model template to check for the existence of optional properties in the input map before assigning values. This prevents errors when optional properties are missing from the input dictionary. (for examle in model: User, :-/ )
Improves the From() method in Model.cs.twig to handle nullable and array properties more robustly, using helper macros for parsing arrays and sub-schemas. This change ensures correct handling of optional fields and type conversions, reducing runtime errors and improving code maintainability. Also removes an unnecessary blank line in ServiceTemplate.cs.twig.
Fields with null values in multipart are now omitted (so they don't turn into empty strings).
Introduces a new parse_value Twig function in DotNet.php to centralize and simplify value parsing logic for model properties. Updates Model.cs.twig to use this function, reducing template complexity and improving maintainability.
Remove null-forgiving operator (!) from optional array mappings and use null-safe casting to preserve null vs empty semantics in generated models.
Adds conditional import of the Enums namespace in the Model.cs.twig template only when the model definition contains enum properties. This prevents unnecessary imports and improves template clarity.
Introduces a ToEnumerable extension method to unify array and enumerable conversions in generated .NET code. Updates code generation logic to use ToEnumerable for array properties, simplifying and improving type safety. Also adds necessary using statement for Extensions in generated model files.
Introduces comprehensive test templates for the .NET SDK, including unit tests for client, models, enums, converters, exceptions, and utility classes. Updates the DotNet language generator to support test file generation and adds new Twig filters and functions to facilitate test code creation.
Introduces a 'Run Tests' step in the sdk-build-validation workflow for multiple SDKs. This step runs the appropriate test command for each SDK and handles cases where no tests are available.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds CI "Run Tests" step to sdk-build-validation and a comprehensive .NET test scaffold plus generator and template changes: new tests, test project/solution entries, DotNet generator helpers/filters, and multiple template refactors and minor API/signature adjustments for generated C# code.
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 17
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (9)
templates/dotnet/Package/Extensions/Extensions.cs.twig (1)
27-50: Encode query strings per key/value; avoid escaping the whole query.
Current code risks malformed queries and locale-sensitive values.+using System.Globalization; @@ - foreach (var kvp in parameters) + foreach (var kvp in parameters) { switch (kvp.Value) { case null: continue; case IList list: foreach (var item in list) { - query.Add($"{kvp.Key}[]={item}"); + var k = Uri.EscapeDataString($"{kvp.Key}[]"); + var v = Uri.EscapeDataString(Convert.ToString(item, CultureInfo.InvariantCulture) ?? string.Empty); + query.Add($"{k}={v}"); } break; default: - query.Add($"{kvp.Key}={kvp.Value.ToString()}"); + var key = Uri.EscapeDataString(kvp.Key); + var val = Uri.EscapeDataString(Convert.ToString(kvp.Value, CultureInfo.InvariantCulture) ?? string.Empty); + query.Add($"{key}={val}"); break; } } - return Uri.EscapeUriString(string.Join("&", query)); + return string.Join("&", query);templates/dotnet/Package/Client.cs.twig (3)
247-291: Dispose HttpResponseMessage to avoid handler/socket leaks.Responses are never disposed. Wrap SendAsync with a using to prevent resource leaks on long‑running clients.
Apply:
- var response = await _httpForRedirect.SendAsync(request); + using var response = await _httpForRedirect.SendAsync( + request, + HttpCompletionOption.ResponseHeadersRead);
302-375: Dispose response; optional: support text responses.
- Same disposal issue as Redirect(); wrap in using.
- Optional: for non‑JSON text content, returning byte[] may be surprising. Consider returning string when typeof(T) == typeof(string) and content-type starts with text/.
- var response = await _http.SendAsync(request); + using var response = await _http.SendAsync(request); @@ - else - { - return ((await response.Content.ReadAsByteArrayAsync()) as T)!; - } + else + { + // Optional: text bodies + if (typeof(T) == typeof(string)) + { + object s = await response.Content.ReadAsStringAsync(); + return (T)s; + } + var bytes = await response.Content.ReadAsByteArrayAsync(); + return (T)(object)bytes; + }
377-542: ChunkedUpload has correctness bugs: off‑by‑one, short reads ignored, wrong offset/progress, and potential stream leak.
- Off‑by‑one: bytes path uses ChunkSize - 1, dropping a byte per chunk.
- Short reads: assumes a single ReadAsync fills the buffer; not guaranteed. Must respect bytesRead and slice ByteArrayContent.
- Offset increment uses ChunkSize, causing gaps/duplication when bytesRead < ChunkSize.
- Progress uses integer division; always 0 until completion.
- Path source stream never disposed.
Fix minimally:
@@ - var size = 0L; - switch(input.SourceType) + var size = 0L; + Stream? stream = null; + bool disposeStream = false; + byte[]? sourceBytes = null; + switch(input.SourceType) { case "path": - var info = new FileInfo(input.Path); - input.Data = info.OpenRead(); - size = info.Length; + var info = new FileInfo(input.Path); + stream = info.OpenRead(); + disposeStream = true; + size = info.Length; break; case "stream": - var stream = input.Data as Stream; - if (stream == null) + stream = input.Data as Stream; + if (stream == null) throw new InvalidOperationException("Stream data is null"); - size = stream.Length; + if (!stream.CanSeek) + throw new InvalidOperationException("Stream must be seekable"); + size = stream.Length; break; case "bytes": - var bytes = input.Data as byte[]; - if (bytes == null) + sourceBytes = input.Data as byte[]; + if (sourceBytes == null) throw new InvalidOperationException("Byte array data is null"); - size = bytes.Length; + size = sourceBytes.Length; break; }; @@ - var buffer = new byte[Math.Min(size, ChunkSize)]; + var buffer = new byte[Math.Min(size, ChunkSize)]; var result = new Dictionary<string, object?>(); @@ - switch(input.SourceType) + switch(input.SourceType) { case "path": case "stream": - var dataStream = input.Data as Stream; - if (dataStream == null) + var dataStream = stream; + if (dataStream == null) throw new InvalidOperationException("Stream data is null"); - await dataStream.ReadAsync(buffer, 0, (int)size); + var remaining = (int)size; + var readTotal = 0; + while (readTotal < remaining) + { + var n = await dataStream.ReadAsync(buffer, readTotal, remaining - readTotal); + if (n == 0) break; + readTotal += n; + } + if (readTotal <= 0) throw new InvalidOperationException("Failed to read data"); break; case "bytes": - var dataBytes = input.Data as byte[]; - if (dataBytes == null) + var dataBytes = sourceBytes; + if (dataBytes == null) throw new InvalidOperationException("Byte array data is null"); - buffer = dataBytes; + buffer = dataBytes; break; } @@ - var content = new MultipartFormDataContent { - { new ByteArrayContent(buffer), paramName, input.Filename } - }; + var content = new MultipartFormDataContent(); + var part = (input.SourceType == "bytes") + ? new ByteArrayContent(buffer) + : new ByteArrayContent(buffer, 0, (int)size); + content.Add(part, paramName, input.Filename); @@ - while (offset < size) + try + { + while (offset < size) { switch(input.SourceType) { case "path": case "stream": - var stream = input.Data as Stream; - if (stream == null) + if (stream == null) throw new InvalidOperationException("Stream data is null"); - stream.Seek(offset, SeekOrigin.Begin); - await stream.ReadAsync(buffer, 0, ChunkSize); + stream.Seek(offset, SeekOrigin.Begin); + var toRead = (int)Math.Min(size - offset, ChunkSize); + if (buffer.Length < toRead) buffer = new byte[toRead]; + var readTotal = 0; + while (readTotal < toRead) + { + var n = await stream.ReadAsync(buffer, readTotal, toRead - readTotal); + if (n == 0) break; + readTotal += n; + } + if (readTotal <= 0) break; break; case "bytes": - buffer = ((byte[])input.Data) - .Skip((int)offset) - .Take((int)Math.Min(size - offset, ChunkSize - 1)) - .ToArray(); + var len = (int)Math.Min(size - offset, ChunkSize); + if (buffer.Length != len) buffer = new byte[len]; + Array.Copy(sourceBytes!, (int)offset, buffer, 0, len); break; } - var content = new MultipartFormDataContent { - { new ByteArrayContent(buffer), paramName, input.Filename } - }; + var content = new MultipartFormDataContent(); + var contentLength = (int)Math.Min(size - offset, ChunkSize); + var part = new ByteArrayContent(buffer, 0, contentLength); + content.Add(part, paramName, input.Filename); @@ - headers["Content-Range"] = - $"bytes {offset}-{Math.Min(offset + ChunkSize - 1, size - 1)}/{size}"; + var end = offset + contentLength - 1; + headers["Content-Range"] = $"bytes {offset}-{end}/{size}"; @@ - offset += ChunkSize; + offset += contentLength; @@ - onProgress?.Invoke( + onProgress?.Invoke( new UploadProgress( id: id, - progress: Math.Min(offset, size) / size * 100, + progress: (double)Math.Min(offset, size) / size * 100d, sizeUploaded: Math.Min(offset, size), chunksTotal: chunksTotal, chunksUploaded: chunksUploaded)); } + } + finally + { + if (disposeStream) + stream?.Dispose(); + }This keeps behavior but fixes correctness and leaks.
templates/dotnet/Package/Models/UploadProgress.cs.twig (1)
1-2: Place UploadProgress under Appwrite.Models for consistencyTo match result types and other templates that reference Appwrite.Models, move this class into the Models namespace.
Apply:
-namespace {{ spec.title | caseUcfirst }} +namespace {{ spec.title | caseUcfirst }}.Models {src/SDK/Language/DotNet.php (1)
162-186: Qualify InputFile with Models namespace to ensure compilation in services without definitionsThe review is correct. InputFile is declared in the
Appwrite.Modelsnamespace (confirmed in template), yet getTypeName returns an unqualifiedInputFileat line 179. This causes compilation failures when a service method uses InputFile but has no model definitions, because:
- ServiceTemplate.cs.twig only generates
using Appwrite.Models;conditionally (ifspec.definitions is not empty)- Without that using statement, unqualified
InputFileis undefined- Other models use the qualified pattern at line 590:
'Appwrite.Models.' . $resultRequired fix:
- self::TYPE_FILE => 'InputFile', + self::TYPE_FILE => 'Appwrite.Models.InputFile',This aligns InputFile with the enum and model qualification convention, ensuring generated code compiles regardless of whether definitions exist.
templates/dotnet/Package/Models/Model.cs.twig (3)
42-56: From(map): safer cast for “data” and alignment with optional inputs.
- data cast can throw if map["data"] isn’t exactly Dictionary<string, object>. Prefer as + coalesce.
- Named args already use removeDollarSign; good.
Apply this change to the data mapping:
- data: map.TryGetValue("data", out var dataValue) ? (Dictionary<string, object>)dataValue : map + data: map.TryGetValue("data", out var dataValue) ? (dataValue as Dictionary<string, object>) ?? map : mapIf any non-required property remains non-nullable, From(...) can still pass null; see prior comment.
58-67: ToMap: potential NullReference on optional nested models/arrays.For sub-schema properties, ToMap() is called unguarded; optional properties can be null. Use null-conditional:
- { "{{ property.name }}", {% if property.sub_schema %}{% if property.type == 'array' %}{{ property_name(definition, property) | overrideProperty(definition.name) }}.Select(it => it.ToMap()){% else %}{{ property_name(definition, property) | overrideProperty(definition.name) }}.ToMap(){% endif %}{% elseif property.enum %}{{ property_name(definition, property) | overrideProperty(definition.name) }}{% if not property.required %}?{% endif %}.Value{% else %}{{ property_name(definition, property) | overrideProperty(definition.name) }}{% endif %}{{ ' }' }}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} + { "{{ property.name }}", {% if property.sub_schema %}{% if property.type == 'array' %}{{ property_name(definition, property) | overrideProperty(definition.name) }}{% if not property.required %}?{% endif %}.Select(it => it.ToMap()){% else %}{{ property_name(definition, property) | overrideProperty(definition.name) }}{% if not property.required %}?{% endif %}.ToMap(){% endif %}{% elseif property.enum %}{{ property_name(definition, property) | overrideProperty(definition.name) }}{% if not property.required %}?{% endif %}.Value{% else %}{{ property_name(definition, property) | overrideProperty(definition.name) }}{% endif %}{{ ' }' }}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %}Prevents NRE when optional nested objects/arrays are absent.
76-82: Duplicate method signature and invalid return/cast in ConvertTo overload confirmed—compile errors require immediate fix.The verification confirms all critical issues:
- Duplicate signatures (lines 70 and 78): Identical method
public T ConvertTo<T>(Func<Dictionary<string, object>, T> fromJson)appears twice and will compile-fail when both conditions are true.- Invalid cast:
(T){{ ... }}.Select(...)castsIEnumerable<T>toT, which is a type error.- Inconsistent naming: Line 79 uses
caseUcfirstwhile the template consistently usesproperty_name(definition, property) | overrideProperty(definition.name)elsewhere (lines 18, 35, 61).Rename and fix the method to return
IEnumerable<T>:- public T ConvertTo<T>(Func<Dictionary<string, object>, T> fromJson) => - (T){{ property.name | caseUcfirst | escapeKeyword }}.Select(it => it.ConvertTo(fromJson)); + public IEnumerable<T> Convert{{ property_name(definition, property) | overrideProperty(definition.name) }}To<T>(Func<Dictionary<string, object>, T> fromJson) => + {{ property_name(definition, property) | overrideProperty(definition.name) }}?.Select(it => it.ConvertTo(fromJson)) ?? Enumerable.Empty<T>();This resolves the signature collision, fixes the type mismatch, uses consistent naming, and handles nulls safely.
🧹 Nitpick comments (18)
templates/dotnet/Package.Tests/IDTests.cs.twig (1)
8-56: Consider additional edge case tests.The current test coverage is solid for common scenarios. For more comprehensive coverage, consider adding tests for:
ID.Custom(null)- to verify null handling behaviorID.Unique(-1)orID.Unique(0)- to verify padding boundary conditionsID.Unique(1000)- to verify behavior with very large padding valuestemplates/dotnet/Package.Tests/Models/InputFileTests.cs.twig (2)
77-108: Consider resource disposal pattern for streams.The MemoryStream instances created in these tests are not explicitly disposed. While the memory impact is negligible in tests and disposing might interfere with verifying the stream is stored correctly, consider adding a comment explaining why disposal is omitted, or restructure tests to validate stream storage and then dispose.
1-217: Consider adding error case tests.The test suite provides excellent coverage of happy paths and edge cases. Consider adding tests for error scenarios to document expected behavior:
FromPathwith null or empty pathFromStreamwith null streamFromByteswith null byte arrayFromFileInfowith null FileInfoThis would clarify whether these methods should throw exceptions or handle nulls gracefully.
templates/dotnet/Package.Tests/.gitignore (1)
1-23: Consider adding a few common ignores.
Optional quality-of-life entries: .DS_Store, .vscode/, BenchmarkDotNet.Artifacts/, coverage.cobertura.xml.Apply this diff:
+# OS/editor +.DS_Store +.vscode/ + +# Benchmarks +BenchmarkDotNet.Artifacts/ + +# Other coverage formats +coverage.cobertura.xmltemplates/dotnet/Package/Exception.cs.twig (1)
22-25: Make inner exception nullable to match BCL and avoid warnings.
System.Exception ctor accepts Exception?; this keeps nullable flow clean.- public {{spec.title | caseUcfirst}}Exception(string message, Exception inner) + public {{spec.title | caseUcfirst}}Exception(string message, Exception? inner) : base(message, inner) { }templates/dotnet/Package.Tests/Tests.csproj.twig (1)
3-8: Enable nullable reference types in tests.
Catches nullability issues early and aligns with SDK.<PropertyGroup> <TargetFramework>net8.0</TargetFramework> <RootNamespace>{{ spec.title | caseUcfirst }}.Tests</RootNamespace> <IsPackable>false</IsPackable> <IsTestProject>true</IsTestProject> + <Nullable>enable</Nullable> </PropertyGroup>templates/dotnet/Package/Converters/ObjectToInferredTypesConverter.cs.twig (1)
10-16: Recursive conversion rewrite: solid improvement; minor enhancements optional.The new Read + ConvertElement flow is clearer and handles nulls properly. Consider (optional) also:
- Prefer DateTimeOffset via TryGetDateTimeOffset for timezone fidelity before falling back to DateTime.
- For large/precise numbers, attempt decimal when representable to avoid double precision loss (guarded by try/catch to stay compatible).
Also applies to: 18-64
templates/dotnet/Package.Tests/Models/ModelTests.cs.twig (2)
300-306: Check all model properties for read‑only, not just the first.Iterate all properties to catch accidental public setters.
- {%~ for property in definition.properties | slice(0, 1) %} + {%~ for property in definition.properties %} var propertyInfo = typeof({{ spec.title | caseUcfirst }}.Models.{{ DefinitionClass }}).GetProperty("{{ property_name(definition, property) | overrideProperty(definition.name) }}"); Assert.NotNull(propertyInfo); Assert.Null(propertyInfo.GetSetMethod()); {%~ endfor %}
1-16: Minor: macro emits only required sub‑schema props.That’s fine for a smoke test. If From() requires optional fields with defaults, consider extending the macro to include a representative optional as well. No action required now.
src/SDK/Language/DotNet.php (5)
545-551: escapeCsString should cover control characters, not only quotes and backslashesEscaping just
\"and\can leak newlines, tabs, etc., into string literals in generated tests.Apply:
- new TwigFilter('escapeCsString', function ($value) { - if (is_string($value)) { - return addcslashes($value, '\\"'); - } - return $value; - }), + new TwigFilter('escapeCsString', function ($value) { + if (is_string($value)) { + // Escape backslash, quote, and common control chars + return addcslashes($value, "\\\"\n\r\t\f\v"); + } + return $value; + }),
616-643: Use toPascalCase instead of ucfirst for schema/enum class namesucfirst won’t handle delimiters; toPascalCase already exists and is used elsewhere.
- $subSchema = \ucfirst($property['sub_schema']); + $subSchema = $this->toPascalCase($property['sub_schema']); ... - $enumClass = \ucfirst($enumName); + $enumClass = $this->toPascalCase($enumName);
645-659: Safer conversions in array parsingDirect casts
(bool)xcan throw for “1”/“true”/0/1. Prefer Convert.* for primitives to match non‑strict JSON inputs.- $selectExpression = match ($itemsType) { - 'string' => 'x.ToString()', - 'integer' => 'Convert.ToInt64(x)', - 'number' => 'Convert.ToDouble(x)', - 'boolean' => '(bool)x', - default => 'x' - }; + $selectExpression = match ($itemsType) { + 'string' => 'x?.ToString()', + 'integer' => 'Convert.ToInt64(x)', + 'number' => 'Convert.ToDouble(x)', + 'boolean' => 'Convert.ToBoolean(x)', + default => 'x' + };
671-679: Boolean parsing for scalars: avoid direct castsUse Convert.ToBoolean for required and nullable branches to accept 0/1/"true"/"false".
- if ($required) { - return "({$typeName}){$mapAccess}"; - } - return "({$typeName}?){$v}"; + if ($required) { + return "Convert.ToBoolean({$mapAccess})"; + } + return "{$v} == null ? (bool?)null : Convert.ToBoolean({$v})";
693-709: Anonymous object generation may emit invalid identifiers for JSON keysformatCSharpAnonymousObject emits
{ key-name = ... }which won’t compile for keys with dashes/spaces. Prefer a Dictionary initializer for arbitrary keys.Consider emitting:
new Dictionary<string, object> { ["key-name"] = ..., ["another key"] = ... }Alternatively, sanitize keys into valid identifiers and document the transformation.
Also applies to: 714-732
templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig (3)
186-198: Actually verify parameter passing instead of Assert.True(true).The “WithParameters_PassesCorrectParameters” test doesn’t validate what was sent. Verify that the payload dictionary contains the expected keys/values (at least for the first few required parameters you materialize).
- // Assert - parameters were set correctly (implicitly tested by successful call) - Assert.True(true); + // Assert - parameters were set correctly on the client call + _mockClient.Verify(c => c.Call<{{ utils.resultType(spec.title, method) }}>( + It.IsAny<string>(), + It.IsAny<string>(), + It.IsAny<Dictionary<string, string>>(), + It.Is<Dictionary<string, object>>(m => + { + var ok = true; + {%~ for parameter in method.parameters.all | filter((param) => param.required) | slice(0, 3) ~%} + ok = ok && m.TryGetValue("{{parameter.name}}", out var v{{loop.index0}}) && Equals(v{{loop.index0}}, {{parameter.name | caseCamel | escapeKeyword}}); + {%~ endfor ~%} + return ok; + }) + {% if method.responseModel %}, It.IsAny<Func<Dictionary<string, object>, {{ utils.resultType(spec.title, method) }}>>() {% else %}, null {% endif %} + ), Times.Once);
124-150: Also verify arguments for Redirect and ChunkedUpload flows.For webAuth (Redirect) and multipart (ChunkedUpload), you only check Times.Once. Add Verify with expected HTTP method/path and non-empty params to catch regressions.
Also applies to: 131-141
9-9: Scope the obsolete warning suppression.#pragma warning disable CS0618 at file scope hides unrelated obsoletions. Narrow it to the minimal region or remove if no longer needed.
templates/dotnet/Package.Tests/Converters/ObjectToInferredTypesConverterTests.cs.twig (1)
174-185: Verify the actual DateTime value.The test only checks that the result is of type
DateTime, but doesn't verify that the date/time was parsed correctly. This leaves a gap in test coverage where parsing could fail silently as long as it returns some DateTime value.Apply this diff to verify the parsed value:
[Fact] public void Read_WithDateTime_ReturnsDateTime() { // Arrange var json = "\"2023-10-16T12:00:00Z\""; // Act var result = JsonSerializer.Deserialize<object>(json, _options); // Assert Assert.IsType<DateTime>(result); + var dateTime = (DateTime)result; + Assert.Equal(new DateTime(2023, 10, 16, 12, 0, 0, DateTimeKind.Utc), dateTime); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (29)
.github/workflows/sdk-build-validation.yml(1 hunks)src/SDK/Language/DotNet.php(4 hunks)templates/dotnet/Package.Tests/.gitignore(1 hunks)templates/dotnet/Package.Tests/ClientTests.cs.twig(1 hunks)templates/dotnet/Package.Tests/Converters/ObjectToInferredTypesConverterTests.cs.twig(1 hunks)templates/dotnet/Package.Tests/Converters/ValueClassConverterTests.cs.twig(1 hunks)templates/dotnet/Package.Tests/Enums/EnumTests.cs.twig(1 hunks)templates/dotnet/Package.Tests/ExceptionTests.cs.twig(1 hunks)templates/dotnet/Package.Tests/IDTests.cs.twig(1 hunks)templates/dotnet/Package.Tests/Models/InputFileTests.cs.twig(1 hunks)templates/dotnet/Package.Tests/Models/ModelTests.cs.twig(1 hunks)templates/dotnet/Package.Tests/PermissionTests.cs.twig(1 hunks)templates/dotnet/Package.Tests/QueryTests.cs.twig(1 hunks)templates/dotnet/Package.Tests/RoleTests.cs.twig(1 hunks)templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig(1 hunks)templates/dotnet/Package.Tests/Tests.csproj.twig(1 hunks)templates/dotnet/Package.Tests/UploadProgressTests.cs.twig(1 hunks)templates/dotnet/Package.sln(1 hunks)templates/dotnet/Package/Client.cs.twig(6 hunks)templates/dotnet/Package/Converters/ObjectToInferredTypesConverter.cs.twig(1 hunks)templates/dotnet/Package/Exception.cs.twig(1 hunks)templates/dotnet/Package/Extensions/Extensions.cs.twig(2 hunks)templates/dotnet/Package/Models/InputFile.cs.twig(2 hunks)templates/dotnet/Package/Models/Model.cs.twig(2 hunks)templates/dotnet/Package/Models/UploadProgress.cs.twig(1 hunks)templates/dotnet/Package/Query.cs.twig(1 hunks)templates/dotnet/Package/Role.cs.twig(2 hunks)templates/dotnet/Package/Services/ServiceTemplate.cs.twig(0 hunks)templates/dotnet/base/utils.twig(1 hunks)
💤 Files with no reviewable changes (1)
- templates/dotnet/Package/Services/ServiceTemplate.cs.twig
🧰 Additional context used
🧬 Code graph analysis (1)
src/SDK/Language/DotNet.php (1)
src/SDK/Language.php (1)
toPascalCase(96-99)
🔇 Additional comments (26)
templates/dotnet/Package.Tests/IDTests.cs.twig (2)
1-58: Solid test coverage for ID utility.The test suite provides good coverage of the ID utility class, including unique ID generation, custom padding, uniqueness verification, and custom string handling with various inputs (empty strings, special characters). The structure properly follows Xunit conventions.
14-14: Verify that ID length is part of the API contract.The tests assert specific lengths (20 for default, 13 + padding for custom padding). If these lengths are implementation details rather than guaranteed API behavior, the tests may break unnecessarily when the ID generation algorithm evolves.
Confirm that these specific length requirements are part of the public API contract. If they're not guaranteed, consider testing for minimum lengths or non-empty results instead.
Also applies to: 24-24
templates/dotnet/Package/Models/InputFile.cs.twig (1)
2-2: LGTM! Namespace parameterization improves template reusability.The change from hardcoded
Appwrite.Extensionsto dynamic{{ spec.title | caseUcfirst }}.Extensionsaligns with the namespace pattern used throughout the template and makes it properly reusable for different SDK projects.templates/dotnet/Package.Tests/Models/InputFileTests.cs.twig (3)
53-75: Excellent resource cleanup pattern.The test properly uses a try-finally block to ensure temp file cleanup, which is good practice even in test code.
110-158: Excellent edge case coverage.The FromBytes tests include good edge case coverage with empty byte arrays and image data scenarios.
202-215: Good defensive testing of default initialization.Validating that the default constructor initializes all properties to non-null values helps prevent NullReferenceExceptions in consuming code.
templates/dotnet/Package/Query.cs.twig (2)
277-277: Formatting-only change — OK.
No behavioral impact.
209-215: Harden Or/And against invalid JSON (avoid nulls).
JsonSerializer.Deserialize can return null; you’d end up serializing null entries. Throw early or filter.
[ suggest_recommended_refactor ]
Apply this diff:- public static string Or(List<string> queries) { - return new Query("or", null, queries.Select(q => JsonSerializer.Deserialize<Query>(q, Client.DeserializerOptions)).ToList()).ToString(); - } + public static string Or(List<string> queries) { + var list = queries + .Select(q => JsonSerializer.Deserialize<Query>(q, Client.DeserializerOptions) + ?? throw new JsonException("Invalid query JSON in Or")) + .ToList(); + return new Query("or", null, list).ToString(); + } - public static string And(List<string> queries) { - return new Query("and", null, queries.Select(q => JsonSerializer.Deserialize<Query>(q, Client.DeserializerOptions)).ToList()).ToString(); - } + public static string And(List<string> queries) { + var list = queries + .Select(q => JsonSerializer.Deserialize<Query>(q, Client.DeserializerOptions) + ?? throw new JsonException("Invalid query JSON in And")) + .ToList(); + return new Query("and", null, list).ToString(); + }Also applies to: 213-215
templates/dotnet/Package.Tests/Tests.csproj.twig (1)
11-22: Update outdated NuGet packages before next release.Several packages in the test project are outdated:
- Microsoft.NET.Test.Sdk: 17.11.1 → 18.0.0 (major version)
- xunit: 2.9.2 → 2.9.3 (patch)
- xunit.runner.visualstudio: 2.8.2 → 3.1.5 (major version)
- coverlet.collector: 6.0.2 → 6.0.4 (patch)
- Moq: 4.20.72 (current)
Verify compatibility before updating, particularly for the major version bumps. Test the project thoroughly after upgrades.
templates/dotnet/Package/Client.cs.twig (3)
91-110: Protected parameterless ctor: good for mocking; confirm header parity.Looks good. One caveat: it omits spec.global.defaultHeaders that the primary ctor injects. If tests or consumers rely on those defaults, proxies created via this ctor may behave differently. Either mirror the same Twig block here or confirm tests set headers explicitly before use.
176-182: Null-check in multipart: LGTM.Skipping null parameter values avoids spurious empty fields in multipart bodies.
293-300: Virtual overload: LGTM.Keeping the non‑generic shim and marking it virtual is fine for mocking.
templates/dotnet/Package.Tests/Models/ModelTests.cs.twig (1)
17-23: Namespace/tests scaffold: good coverage shape.The test set exercises construction, maps, round‑trip, convert‑to, and immutability. Once the namespace fix lands, this template looks solid.
templates/dotnet/Package/Role.cs.twig (1)
1-1: Namespace templating: LGTM.Aligns with the rest of the templated code and the new tests’ namespaces.
templates/dotnet/Package.Tests/PermissionTests.cs.twig (1)
1-77: Solid, focused coverage of permission format helpersAssertions cover default and parameterized roles across all verbs. Looks good.
templates/dotnet/Package.Tests/UploadProgressTests.cs.twig (1)
8-28: Solid coverage for UploadProgress getters and invariants.Also applies to: 30-40, 42-52, 54-64, 66-76, 78-88, 90-100, 102-112, 114-122, 124-135, 137-145, 147-164
templates/dotnet/Package.Tests/ExceptionTests.cs.twig (1)
66-78: Constructor overload confirmed—no changes needed.The verification confirms that the
{{spec.title | caseUcfirst}}Exception(string message, Exception inner)constructor exists at lines 22-25 ofException.cs.twigand correctly delegates to the base class viabase(message, inner). The test properly exercises this overload as expected.templates/dotnet/Package.Tests/QueryTests.cs.twig (1)
13-22: JSON property mapping is correctly configured—no changes needed.Verification confirms that the Query class in
templates/dotnet/Package/Query.cs.twighas explicit[JsonPropertyName]decorators for all three properties (method, attribute, values) that match the lowercase keys used during JSON deserialization in QueryTests.cs.twig. Deserialization will work as expected.templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig (1)
35-37: No changes needed—Moq requirements are satisfied.Verification confirms that all three Client methods are virtual:
Call(line 293),ChunkedUpload(line 377), andRedirect(line 247). The Client class is also not sealed (line 17). The code is already properly configured for Moq mocking.templates/dotnet/Package.Tests/ClientTests.cs.twig (1)
12-35: LGTM - Well-structured client tests.The remaining test methods provide good coverage of the Client class functionality with appropriate assertions:
- Constructor variants with proper verification
- Fluent API methods with state verification
- Property accessors with type checks
- Serialization options validation
- Method chaining behavior
Also applies to: 60-73, 91-115, 145-214
templates/dotnet/Package.Tests/RoleTests.cs.twig (1)
1-108: LGTM - Comprehensive role string formatting tests.The test suite provides excellent coverage of all Role factory methods with clear, focused assertions. Each test verifies the exact string format produced by the role builders, which is critical for ensuring correct permission strings in the SDK.
The test structure is consistent and easy to maintain, with descriptive test names that clearly indicate what's being tested.
templates/dotnet/Package.Tests/Converters/ObjectToInferredTypesConverterTests.cs.twig (1)
1-18: LGTM - Comprehensive converter tests.The test suite provides excellent coverage of the
ObjectToInferredTypesConverterfunctionality:
- Read tests: Verify correct type inference for all JSON value types (primitives, objects, arrays, nested structures, edge cases)
- Write tests: Confirm proper serialization of various .NET types back to JSON
- Round-trip test: Validates data preservation through serialize/deserialize cycles
The test structure is well-organized with clear AAA (Arrange-Act-Assert) pattern, and assertions properly verify both types and values.
Also applies to: 19-173, 188-215, 217-287, 289-311
templates/dotnet/Package/Models/Model.cs.twig (4)
1-1: Macro-driven class naming looks good.Using DefinitionClass with overrideIdentifier reduces reserved-name collisions in generated code.
7-9: Conditional Enums using is correct.Keeps imports minimal and resolves enum type references only when needed.
14-14: Class identifier switch to DefinitionClass is aligned with the macro approach.No issues.
18-18: Add removeDollarSign filter and = default parameters to constructor; ensure nullability encoding for non-required properties.The review comment identifies three valid issues in
templates/dotnet/Package/Models/Model.cs.twig:
Line 27 (constructor parameters): Missing
removeDollarSignfilter, creating inconsistency with theFrom()method (line 46) which already applies it. Dollar signs in property names like$idwould appear as literal$idin named arguments instead of being escaped.Line 27: Non-required properties lack
= defaultparameter defaults, forcing callers to provide explicit values or resort to positional arguments. This reduces ergonomics compared to other language templates (e.g., Swift adds?and makes nulls explicit).Nullability encoding: The
sub_schema(property)macro should return nullable types (e.g.,string?/int?) for non-required properties to prevent null-reference exceptions at runtime or compilation failures for value types.Apply the suggested diff and verify the macro handles C# null-safety for non-required properties:
- {{ sub_schema(property) }} {{ property.name | caseCamel | escapeKeyword }}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} + {{ sub_schema(property) }} {{ property.name | caseCamel | escapeKeyword | removeDollarSign }}{% if not property.required %} = default{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %}Confirm that
sub_schema(property)emits nullable forms (string?,int?, etc.) whenproperty.requiredis false, matching the pattern used in Swift and other language implementations.Likely an incorrect or invalid review comment.
Update InputFileTests to account for platform-specific filename handling by checking the OS and adjusting the expected filename accordingly.
Replaced hardcoded 'Appwrite' namespace references with dynamic '{{ spec.title | caseUcfirst }}' in model and service test templates.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/SDK/Language/DotNet.php (1)
587-587: Docblock typo persists.The typo "sub_scheme" → "sub_schema" was already flagged in a previous review but remains unfixed.
🧹 Nitpick comments (2)
templates/dotnet/Package.Tests/OperatorTests.cs.twig (1)
7-334: Consider adding edge case tests for validation logic.The Operator class includes validation for NaN, Infinity, and division by zero, but these edge cases aren't tested. Consider adding tests to verify the ArgumentException behavior for:
- NaN and Infinity values in arithmetic operations
- Zero divisor in Divide and Modulo
- Null arguments in array operations
src/SDK/Language/DotNet.php (1)
577-582: Consider whether additional escape sequences are needed.The
escapeCsStringfilter currently escapes backslashes and double quotes, which covers the most common cases for C# string literals. If template values might contain newlines, tabs, or other control characters, you may want to extend this. However, for typical test data from API specifications, the current implementation is likely sufficient.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/SDK/Language/DotNet.php(4 hunks)templates/dotnet/Package.Tests/OperatorTests.cs.twig(1 hunks)templates/dotnet/Package.Tests/QueryTests.cs.twig(1 hunks)templates/dotnet/Package/Operator.cs.twig(1 hunks)templates/dotnet/Package/Query.cs.twig(2 hunks)
✅ Files skipped from review due to trivial changes (1)
- templates/dotnet/Package.Tests/QueryTests.cs.twig
🚧 Files skipped from review as they are similar to previous changes (1)
- templates/dotnet/Package/Query.cs.twig
🧰 Additional context used
🧬 Code graph analysis (1)
src/SDK/Language/DotNet.php (1)
src/SDK/Language.php (2)
toPascalCase(115-118)getTypeName(63-63)
🔇 Additional comments (4)
templates/dotnet/Package/Operator.cs.twig (1)
82-82: LGTM! Modifier order now follows C# conventions.The change aligns the modifier order with C# best practices (access modifier before method modifier).
src/SDK/Language/DotNet.php (3)
476-554: LGTM! Comprehensive test infrastructure.The test file configuration entries are well-organized and cover all necessary test scopes: common SDK behaviors, per-model tests, per-enum tests, and per-service tests. The destination paths and template references follow consistent conventions.
615-638: LGTM! Well-structured type resolution for test templates.The
test_item_typefunction correctly handles different property structures (models, enums, primitive arrays) and appropriately returns fully qualified names for custom types while using simple names for primitives. The logic is comprehensive and clear.
648-718: LGTM! Comprehensive value parsing logic.The
parse_valuefunction is well-designed and handles all major type scenarios comprehensively:
- Models and enums with proper class instantiation
- Arrays with type-appropriate conversions
- Primitives with correct Convert/cast operations
- Nullable vs required fields throughout
The generated C# expressions appear correct for the intended test template usage.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/SDK/Language/DotNet.php (1)
587-589: Typo in docblock: "sub_scheme" → "sub_schema".
🧹 Nitpick comments (1)
src/SDK/Language/DotNet.php (1)
578-583: Consider escaping additional C# special characters.The filter currently escapes only backslashes and double quotes. If template values contain newlines, tabs, or other control characters, the generated C# code could have syntax errors.
new TwigFilter('escapeCsString', function ($value) { if (is_string($value)) { - return addcslashes($value, '\\"'); + // Escape backslash first, then other special characters + $value = str_replace('\\', '\\\\', $value); + $value = str_replace('"', '\\"', $value); + $value = str_replace("\n", '\\n', $value); + $value = str_replace("\r", '\\r', $value); + $value = str_replace("\t", '\\t', $value); + return $value; } return $value; }),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.github/workflows/sdk-build-validation.yml(1 hunks)src/SDK/Language/DotNet.php(4 hunks)templates/dotnet/Package/Query.cs.twig(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- .github/workflows/sdk-build-validation.yml
🔇 Additional comments (4)
templates/dotnet/Package/Query.cs.twig (1)
43-46: LGTM! Modifier order now follows C# conventions.The change from
override publictopublic overridealigns with C# style guidelines where access modifiers precede other modifiers.src/SDK/Language/DotNet.php (3)
476-555: Well-structured test scaffolding configuration.The test file mappings follow the existing patterns consistently. The scope assignments (
definition,enum,service) are appropriate for generating per-entity test files, and the naming conventions align with the main SDK templates.
616-639: LGTM!The function correctly determines item types for test generation with appropriate namespace prefixing. The fallback chain (sub_schema → enum → items → array → object) handles the various property structures well.
649-719: Verify thatToEnumerable()extension method is properly defined in the generated SDK.The code uses
ToEnumerable()in generated expressions (lines 658, 691), but this is not a standard .NET/LINQ method. It must be a custom extension defined in the generated SDK. Confirm that the generated C# code includes this extension definition or the appropriate using statements to access it, otherwise the generated code will fail to compile.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig (1)
58-63: Escape string examples to prevent syntax errors in generated C#.String property examples at line 61 are not escaped, which will cause invalid C# when
x-examplecontains quotes, newlines, or backslashes. This issue was previously flagged but remains unresolved.Apply the same fix as suggested in the previous review:
- { "{{property.name}}", {% if property.enum %}{{ property.enumName | caseUcfirst }}.{{ (property.enumKeys[0] ?? property.enum[0]) | caseEnumKey }}.Value{% elseif property.type == 'string' %}"{{property['x-example'] | default('test')}}"{% elseif property.type == 'boolean' %}true{% elseif property.type == 'integer' %}{{property['x-example'] | default(1)}}{% elseif property.type == 'number' %}{{property['x-example'] | default(1.0)}}{% elseif property.type == 'array' %}new List<object>(){% elseif property.type == 'object' and not property.sub_schema %}new Dictionary<string, object>(){% elseif property.sub_schema %}{{ _self.generate_sub_dict(spec.definitions | filter(d => d.name == property.sub_schema) | first) }}{% else %}null{% endif %} }, + { "{{property.name}}", {% if property.enum %}{{ property.enumName | caseUcfirst }}.{{ (property.enumKeys[0] ?? property.enum[0]) | caseEnumKey }}.Value{% elseif property.type == 'string' %}"{{property['x-example'] | default('test') | escapeCsString}}"{% elseif property.type == 'boolean' %}true{% elseif property.type == 'integer' %}{{property['x-example'] | default(1)}}{% elseif property.type == 'number' %}{{property['x-example'] | default(1.0)}}{% elseif property.type == 'array' %}new List<object>(){% elseif property.type == 'object' and not property.sub_schema %}new Dictionary<string, object>(){% elseif property.sub_schema %}{{ _self.generate_sub_dict(spec.definitions | filter(d => d.name == property.sub_schema) | first) }}{% else %}null{% endif %} },
🧹 Nitpick comments (2)
templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig (2)
157-159: Escape string parameter names in test data.Line 158 concatenates parameter names directly into string literals (
"test{{parameter.name}}"). If a parameter name contains special characters (though unlikely), this could break. More importantly, for consistency with the rest of the template, string construction should use proper escaping patterns.Consider applying this diff for consistency:
- {% if parameter.type == 'file' %}InputFile{% else %}var{% endif %} {{parameter.name | caseCamel | escapeKeyword}} = {% if parameter.enumValues is not empty %}{{ spec.title | caseUcfirst }}.Enums.{{ (parameter.enumName ?? parameter.name) | caseUcfirst }}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% elseif parameter.type == 'file' %}InputFile.FromPath("./test.png"){% elseif parameter.type == 'object' %}new Dictionary<string, object>(){% elseif parameter.type == 'array' %}{% set itemType = test_item_type(parameter) %}new List<{{ itemType }}> { {% if itemType == 'string' %}"item1"{% elseif itemType == 'long' %}1{% elseif itemType == 'double' %}1.0{% elseif itemType == 'bool' %}true{% elseif itemType == 'object' %}new object(){% else %}null{% endif %} }{% elseif parameter.type == 'boolean' %}true{% elseif parameter.type == 'integer' or parameter.type == 'number' %}{{parameter['x-example'] | default(123)}}{% elseif parameter.type == 'string' %}"test{{parameter.name}}"{% else %}null{% endif %}; + {% if parameter.type == 'file' %}InputFile{% else %}var{% endif %} {{parameter.name | caseCamel | escapeKeyword}} = {% if parameter.enumValues is not empty %}{{ spec.title | caseUcfirst }}.Enums.{{ (parameter.enumName ?? parameter.name) | caseUcfirst }}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% elseif parameter.type == 'file' %}InputFile.FromPath("./test.png"){% elseif parameter.type == 'object' %}new Dictionary<string, object>(){% elseif parameter.type == 'array' %}{% set itemType = test_item_type(parameter) %}new List<{{ itemType }}> { {% if itemType == 'string' %}"item1"{% elseif itemType == 'long' %}1{% elseif itemType == 'double' %}1.0{% elseif itemType == 'bool' %}true{% elseif itemType == 'object' %}new object(){% else %}null{% endif %} }{% elseif parameter.type == 'boolean' %}true{% elseif parameter.type == 'integer' or parameter.type == 'number' %}{{parameter['x-example'] | default(123)}}{% elseif parameter.type == 'string' %}"test{{parameter.name | escapeCsString}}"{% else %}null{% endif %};
195-197: No-op assertion provides no test value.
Assert.True(true)verifies nothing. The comment claims parameters are "implicitly tested by successful call," but a successful mock invocation doesn't prove the correct parameter values were passed.Consider either:
- Remove this test entirely if it adds no value, or
- Add a meaningful assertion that captures and verifies the actual parameter dictionary passed to the mock using
Callback.Example using
Callbackto verify parameters:+ Dictionary<string, object> capturedParams = null; _mockClient.Setup(c => c.Call<{{ utils.resultType(spec.title, method) }}>( It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Dictionary<string, string>>(), - It.IsAny<Dictionary<string, object>>(){% if method.responseModel %}, + It.IsAny<Dictionary<string, object>>(){% if method.responseModel %}, It.IsAny<Func<Dictionary<string, object>, {{ utils.resultType(spec.title, method) }}>>() {% else %},null{% endif %} - )).ReturnsAsync({% if method.responseModel and method.responseModel != 'any' and method.type != 'location' %}{{ spec.title | caseUcfirst }}.Models.{{ method.responseModel | caseUcfirst | overrideIdentifier }}.From(expectedResponse){% else %}expectedResponse{% endif %}); + )).Callback<string, string, Dictionary<string, string>, Dictionary<string, object>{% if method.responseModel %}, Func<Dictionary<string, object>, {{ utils.resultType(spec.title, method) }}>{% endif %}>((m, p, h, pm{% if method.responseModel %}, f{% endif %}) => capturedParams = pm) + .ReturnsAsync({% if method.responseModel and method.responseModel != 'any' and method.type != 'location' %}{{ spec.title | caseUcfirst }}.Models.{{ method.responseModel | caseUcfirst | overrideIdentifier }}.From(expectedResponse){% else %}expectedResponse{% endif %}); // Act ... // Assert - parameters were set correctly - Assert.True(true); + Assert.NotNull(capturedParams); + {%~ for parameter in method.parameters.all | filter((param) => param.required) | slice(0, 3) ~%} + Assert.Equal({{parameter.name | caseCamel | escapeKeyword}}, capturedParams["{{parameter.name}}"]); + {%~ endfor ~%}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig(1 hunks)
🔇 Additional comments (1)
templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig (1)
2-8: LGTM! Macro properly escapes strings.The
generate_sub_dictmacro correctly appliesescapeCsStringto string properties, which serves as a good pattern for the rest of the template.
Added specific mock setups for 'webAuth' and 'multipart/form-data' method types in service tests. Also fixed string escaping for x-example values in string parameters to ensure valid C# code generation.
Replaces Assert.Equal with Assert.True/Assert.False for boolean properties and adds type checks for NotNull assertions on required properties in ModelTests.cs.twig. Improves accuracy and clarity of generated test code.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In @.github/workflows/sdk-build-validation.yml:
- Around line 220-221: The test step for the "web|node|cli|react-native)" case
is inconsistent with the CLI build that uses "bun install" and "bun run"; update
the test command to prefer Bun and fall back to npm to avoid failures: replace
the current "npm test || echo 'No tests available'" with a command such as "bun
test || npm test || echo 'No tests available'" so the CLI target runs Bun tests
when available (referencing the case label "web|node|cli|react-native)" and the
build commands "bun install" / "bun run").
In `@src/SDK/Language/DotNet.php`:
- Around line 690-760: The parse_value TwigFunction has two bugs: it uses
\ucfirst($property['sub_schema']) instead of the class helper (breaking
snake/kebab names) and it calls .ToEnumerable() on optional arrays without
null-checks; fix by changing the sub-schema casing to use
$this->toPascalCase($property['sub_schema']) when computing $subSchema, and add
null-guards when !$required for both the sub_schema array branch and the general
array branch (e.g. use a ternary that returns null when {$v} is null, otherwise
call .ToEnumerable().Select(...).ToList()), keeping existing use of $mapAccess
when $required is true.
- Around line 590-595: The TwigFilter callback named 'escapeCsString' in
DotNet.php currently only escapes backslashes and double quotes which allows raw
control characters (newline, tab, carriage return, null, backspace, form feed)
to break generated C# string literals; update the filter to replace/escape these
control characters into their C# escape sequences (e.g. \n, \r, \t, \0, \b, \f)
in addition to escaping backslashes and quotes so the output is always a valid
C# string literal, keeping the is_string($value) check and returning non-strings
unchanged.
♻️ Duplicate comments (2)
.github/workflows/sdk-build-validation.yml (1)
216-256: Test failures are masked by the|| echo "No tests available"fallback.The
|| echo "No tests available"pattern causes the step to succeed even when tests exist but fail, defeating the purpose of CI validation. Each SDK branch should first detect whether tests are present, then run them without masking failures.src/SDK/Language/DotNet.php (1)
629-630: Docblock typo: “sub_scheme” → “sub_schema”.
🧹 Nitpick comments (1)
templates/dotnet/base/utils.twig (1)
15-15: Use thenamespaceargument instead of hardcodingAppwriteThis macro now ignores its
namespaceparameter. If the generator ever targets a non‑Appwrite namespace (or the namespace changes), this will produce incorrect types. Consider interpolating the passed namespace (or remove the parameter if it’s intentionally fixed).♻️ Proposed refactor
-{% if method.type == "webAuth" %}bool{% elseif method.type == "location" %}byte[]{% elseif method.responseModels|length > 1 %}object{% elseif not method.responseModel or method.responseModel == 'any' %}object{% else %}Appwrite.Models.{{method.responseModel | caseUcfirst | overrideIdentifier }}{% endif %} +{% if method.type == "webAuth" %}bool{% elseif method.type == "location" %}byte[]{% elseif method.responseModels|length > 1 %}object{% elseif not method.responseModel or method.responseModel == 'any' %}object{% else %}{{ namespace }}.Models.{{method.responseModel | caseUcfirst | overrideIdentifier }}{% endif %}
Introduce centralized property conversion and mapping helpers for the .NET SDK generator and update model template to use them. Changes: - Add Twig filters: propertyAssignment and toMapValue. - Make getPropertyType optionally return non-fully-qualified names. - Replace inline parsing logic with new methods: getPropertyName, getResolvedPropertyName, getPropertyAssignment, convertValue, getToMapExpression. These handle enums, sub-schemas, arrays, primitive conversions and null-safety in a unified way. - Simplify getFunctions() implementation to delegate to the new helpers. - Update templates/dotnet/Package/Models/Model.cs.twig to use the new filters (propertyAssignment, toMapValue) and clean up From/ToMap generation. Reason: centralizes and standardizes property (de)serialization logic, improves null handling, supports property overrides, and simplifies the model template.
Adjust .NET SDK generation and test templates: - DotNet: prepend Appwrite.Enums to enum example resolution (enumFullName) to generate fully-qualified enum references. - Client tests: remove redundant comments, rename SetHeader test to AddHeader, add dynamic per-global-header SetX/Config assertions, tighten serializer/deserializer assertions and chained call checks. - Model tests: tighten property assertions to validate enums, strings, numbers, booleans, and complex types with specific example values. - Service tests: use enumExample helper for enum parameters, simplify array/list initializers and example values, and improve expected response value generation using example hints. - Operator tests: add numerous input validation tests (NaN, Infinity, zero/divide, null array ops) and include System import. - Permission & Role tests: simplify assertions by inlining expected calls. - Query tests: add many spatial/geometric query tests (distance*, intersects, crosses, overlaps, touches) including meter flag variations. - UploadProgress tests: update example values, remove redundant asserts/comments, and consolidate read-only property checks into a single test.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
templates/dotnet/Package/Models/Model.cs.twig (1)
67-78:⚠️ Potential issue | 🔴 CriticalDuplicate method signatures and return type mismatch in ConvertTo generation.
This block (lines 67-78) has two critical issues:
Duplicate method signatures: The nested loop iterates over all properties and generates
ConvertTo<T>(Func<Dictionary<string, object>, T> fromJson)for each property matching the filter conditions (sub_schema, additionalProperties definition, array type). If a model has multiple array properties with sub_schemas pointing to additionalProperties definitions, this generates multiple methods with identical signatures, causing a C# compilation error.Runtime type cast failure: Line 73 casts
Select(...)toT. Since the loop condition enforcesproperty.type == 'array'(line 70), the property is a collection. The.Select()method returnsIEnumerable<T>, notT. This cast fails at runtime for any concreteTthat isn't itself an enumerable.Both must be fixed to avoid breaking generated code. Consider using a distinct method name for the array case (e.g.,
ConvertItemsTo<T>) with return typeIEnumerable<T>, or ensuring only one such method is ever generated per class.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@templates/dotnet/Package/Models/Model.cs.twig` around lines 67 - 78, The generated ConvertTo<T>(Func<Dictionary<string, object>, T> fromJson) methods produce duplicate signatures and an incorrect return type for array properties; update the template so array properties generate a distinct method (e.g., ConvertItemsTo<T>) that returns IEnumerable<T> and uses Select(...). Specifically, in Model.cs.twig change the branch that matches property.type == 'array' and property.sub_schema to emit ConvertItemsTo<T>(Func<Dictionary<string, object>, T> fromJson) returning IEnumerable<T> (and call {{ property.name | caseUcfirst | escapeKeyword }}.Select(...)), and ensure you do not emit multiple methods with the same signature per class (either by naming the array variant differently or by generating a single shared method per class).
♻️ Duplicate comments (6)
templates/dotnet/Package.Tests/OperatorTests.cs.twig (2)
193-193:⚠️ Potential issue | 🟡 MinorUse
GetString()for reliable string extraction from JsonElement.Using
ToString()on aJsonElementdoesn't reliably extract the actual string value—it may include quotes or other artifacts. UseGetString()for proper string extraction.Proposed fix pattern
- Assert.Equal("newItem", op.Values[1].ToString()); + Assert.Equal("newItem", ((JsonElement)op.Values[1]).GetString());Apply to lines 193, 206, 255, 256, 269, 282, 283.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@templates/dotnet/Package.Tests/OperatorTests.cs.twig` at line 193, Replace usages of JsonElement.ToString() with JsonElement.GetString() in the OperatorTests assertions so string values are extracted reliably; specifically update all assertions and comparisons that call ToString() on elements inside op.Values (and any other JsonElement instances in OperatorTests) to use GetString() instead (apply the same change to all occurrences suggested in the review). Ensure you keep the existing Assert.Equal calls and only swap the accessor method from ToString() to GetString() so the tests assert the actual string content.
20-20:⚠️ Potential issue | 🟡 MinorUse
GetDouble()for numeric assertions to match parameter types.The Operator methods accept
doubleparameters, but the test usesGetInt32()for assertions. This may fail if the JSON contains decimal values or if values exceed int32 range. UseGetDouble()with double literals for consistency.Proposed fix pattern
- Assert.Equal(1, ((JsonElement)op.Values[0]).GetInt32()); + Assert.Equal(1.0, ((JsonElement)op.Values[0]).GetDouble());Apply this pattern to all numeric assertions at lines 20, 33-34, 47, 60-61, 74, 87-88, 101, 114-115, 128, 141, 154-155, 192, 308, 321.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@templates/dotnet/Package.Tests/OperatorTests.cs.twig` at line 20, The test uses GetInt32() to assert numeric values in OperatorTests (e.g., the Assert.Equal(1, ((JsonElement)op.Values[0]).GetInt32()) line) but Operator methods accept double parameters; change those assertions to use GetDouble() and compare against double literals (e.g., 1.0) to avoid truncation/range issues and ensure consistency; update every numeric assertion referencing op.Values[...] (lines noted in the review) to call GetDouble() and use double expected values.templates/dotnet/Package.Tests/Models/ModelTests.cs.twig (1)
71-72:⚠️ Potential issue | 🟡 MinorSeparate integer and number assertions for type accuracy.
Lines 71-72 combine integer and number types but use the same integer literal
default(1). Fornumber(double) properties, this can cause type mismatches in assertions since C# distinguishes1(int) from1.0(double).Proposed fix
- {%~ elseif property.type == 'integer' or property.type == 'number' %} - Assert.Equal({{ property['x-example'] | default(1) }}, model.{{ property_name(definition, property) | overrideProperty(definition.name) }}); + {%~ elseif property.type == 'integer' %} + Assert.Equal({{ property['x-example'] | default(1) }}, model.{{ property_name(definition, property) | overrideProperty(definition.name) }}); + {%~ elseif property.type == 'number' %} + Assert.Equal({{ property['x-example'] | default(1.0) }}, model.{{ property_name(definition, property) | overrideProperty(definition.name) }});Apply the same pattern to lines 170-171.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@templates/dotnet/Package.Tests/Models/ModelTests.cs.twig` around lines 71 - 72, The template currently treats 'integer' and 'number' the same, causing integer literals to be used for double properties; update the conditional in ModelTests.cs.twig so that when property.type == 'integer' you Assert.Equal using the integer example/default (1) and when property.type == 'number' you Assert.Equal using a floating literal (e.g., 1.0 or 1.0D) to match C# double types; apply the same change to the second occurrence referenced around lines 170-171. Use the existing helpers property.type, property['x-example'], and property_name(definition, property) / overrideProperty(definition.name) to locate and emit the correct literal per type.src/SDK/Language/DotNet.php (1)
601-606:⚠️ Potential issue | 🟠 MajorEscape control characters in
escapeCsStringfilter.The current implementation only escapes backslashes and double quotes. If spec values contain newline, tab, carriage return, or null characters, the generated C# string literals will be syntactically invalid.
Proposed fix
new TwigFilter('escapeCsString', function ($value) { if (is_string($value)) { - return addcslashes($value, '\\"'); + return strtr($value, [ + "\\" => "\\\\", + "\"" => "\\\"", + "\r" => "\\r", + "\n" => "\\n", + "\t" => "\\t", + "\0" => "\\0", + ]); } return $value; }),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/SDK/Language/DotNet.php` around lines 601 - 606, The escapeCsString Twig filter currently only escapes backslashes and double quotes causing newlines/tabs/etc. to break generated C# strings; update the closure used in the new TwigFilter('escapeCsString', ...) so it also replaces control characters with C# escape sequences (at minimum: newline "\n" -> "\\n", carriage return "\r" -> "\\r", tab "\t" -> "\\t", null "\0" -> "\\0", and optionally backspace "\b" and formfeed "\f"), while still escaping backslashes and double quotes; implement this using a deterministic replacement (e.g., str_replace or a single preg_replace_callback) within the existing closure so all control characters are converted to their escaped representations before returning the string.templates/dotnet/Package.Tests/ClientTests.cs.twig (1)
62-73:⚠️ Potential issue | 🟡 MinorApply
escapeCsStringfilter to header key values.The
{{header.key}}values are inserted directly into C# strings without escaping. If a header key contains special characters (quotes, backslashes), the generated code will have syntax errors.Proposed fix
[Theory] {%~ for header in spec.global.headers %} - [InlineData("{{header.key}}", "test-{{header.key}}")] + [InlineData("{{header.key | escapeCsString}}", "test-{{header.key | escapeCsString}}")] {%~ endfor %}Also apply to lines 80, 132, and 139 where
{{header.key}}appears in string contexts.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@templates/dotnet/Package.Tests/ClientTests.cs.twig` around lines 62 - 73, The template inserts raw header keys into C# string literals causing syntax errors for quotes/backslashes; update each occurrence of {{header.key}} used inside C# string contexts (e.g., the InlineData attributes in the AddHeader_SetsCustomHeader test and the other occurrences noted around lines with AddHeader usage) to use the escapeCsString filter so the values are escaped for C# (apply escapeCsString to the InlineData values and the other three spots where "{{header.key}}" is rendered).templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig (1)
60-62:⚠️ Potential issue | 🟡 MinorMissing
escapeCsStringfilter for string property examples.Line 61 inserts
property['x-example']directly into C# string literals without escaping. This is inconsistent with the fix applied elsewhere in this template (lines 104, 165) and could generate invalid C# if anx-examplecontains quotes, backslashes, or newlines.Proposed fix
- { "{{property.name}}", {% if property.enum %}{{ property.enumName | caseUcfirst }}.{{ (property.enumKeys[0] ?? property.enum[0]) | caseEnumKey }}.Value{% elseif property.type == 'string' %}"{{property['x-example'] | default('test')}}"{% elseif property.type == 'boolean' %}true{% elseif property.type == 'integer' %}{{property['x-example'] | default(1)}}{% elseif property.type == 'number' %}{{property['x-example'] | default(1.0)}}{% elseif property.type == 'array' %}new List<object>(){% elseif property.type == 'object' and not property.sub_schema %}new Dictionary<string, object>(){% elseif property.sub_schema %}{{ _self.generate_sub_dict(spec.definitions | filter(d => d.name == property.sub_schema) | first) }}{% else %}null{% endif %} }, + { "{{property.name}}", {% if property.enum %}{{ property.enumName | caseUcfirst }}.{{ (property.enumKeys[0] ?? property.enum[0]) | caseEnumKey }}.Value{% elseif property.type == 'string' %}"{{property['x-example'] | default('test') | escapeCsString}}"{% elseif property.type == 'boolean' %}true{% elseif property.type == 'integer' %}{{property['x-example'] | default(1)}}{% elseif property.type == 'number' %}{{property['x-example'] | default(1.0)}}{% elseif property.type == 'array' %}new List<object>(){% elseif property.type == 'object' and not property.sub_schema %}new Dictionary<string, object>(){% elseif property.sub_schema %}{{ _self.generate_sub_dict(spec.definitions | filter(d => d.name == property.sub_schema) | first) }}{% else %}null{% endif %} },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig` around lines 60 - 62, The template inserts property['x-example'] directly into C# string literals for required string properties (inside the loop over spec.definitions / property in ServiceTests.cs.twig), which can produce invalid C# when that example contains quotes, backslashes, or newlines; update the string branch (the elseif property.type == 'string' case) to wrap the example with the existing escapeCsString filter (same as used at lines 104 and 165) so examples are escaped before being placed into the C# literal, and ensure any default('test') fallback is also processed by escapeCsString; reference the string handling branch in the for loop and the escapeCsString utility used elsewhere (and keep generate_sub_dict unchanged).
🧹 Nitpick comments (4)
src/SDK/Language/DotNet.php (1)
736-738: Remove unused$definitionparameter.The
property_nameTwigFunction receives$definitionbut only uses$property. This was flagged by static analysis.Proposed fix
- new TwigFunction('property_name', function (array $definition, array $property) { + new TwigFunction('property_name', function (array $property) { return $this->getPropertyName($property); }),Note: This would require updating all template usages of
property_name(definition, property)toproperty_name(property).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/SDK/Language/DotNet.php` around lines 736 - 738, The closure registered for the TwigFunction 'property_name' currently accepts (array $definition, array $property) but only uses $property; remove the unused $definition parameter from the closure signature so it becomes function (array $property) and call $this->getPropertyName($property) inside it, and then update any templates that call property_name(definition, property) to call property_name(property) to match the new signature; ensure the change is applied where the TwigFunction is constructed and any template usages are updated accordingly.templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig (2)
214-215: Test assertion is a no-op.
Assert.True(true)provides no validation. The comment states parameters were "implicitly tested by successful call," but if the mock usesIt.IsAny<>()matchers, incorrect parameters would still pass. Consider either verifying specific parameter values or removing this test method if it adds no value.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig` around lines 214 - 215, The test contains a no-op assertion Assert.True(true); replace it with meaningful verification: either assert the expected return/state values from the tested method or verify the mocked dependency received the correct parameters (use mock.Verify(..., Times.Once()) or change It.IsAny<> to It.Is<T>(p => p.Property == expected) to assert parameter values). If the method truly has no observable effects and the mock interactions are already asserted elsewhere, remove the empty test to avoid false confidence.
193-201: Inconsistent indentation in mock setup block.Lines 194-201 have an extra leading space compared to the equivalent blocks at lines 90-97 and 73-78. This creates inconsistent formatting in generated test files.
Proposed fix
{%~ else %} - _mockClient.Setup(c => c.Call<{{ utils.resultType(spec.title, method) }}>( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<Dictionary<string, string>>(), - It.IsAny<Dictionary<string, object>>(){% if method.responseModel %}, - It.IsAny<Func<Dictionary<string, object>, {{ utils.resultType(spec.title, method) }}>>() - {% else %},null{% endif %} - )).ReturnsAsync({% if method.responseModel and method.responseModel != 'any' and method.type != 'location' %}{{ spec.title | caseUcfirst }}.Models.{{ method.responseModel | caseUcfirst | overrideIdentifier }}.From(expectedResponse){% else %}expectedResponse{% endif %}); + _mockClient.Setup(c => c.Call<{{ utils.resultType(spec.title, method) }}>( + It.IsAny<string>(), + It.IsAny<string>(), + It.IsAny<Dictionary<string, string>>(), + It.IsAny<Dictionary<string, object>>(){% if method.responseModel %}, + It.IsAny<Func<Dictionary<string, object>, {{ utils.resultType(spec.title, method) }}>>() + {% else %},null{% endif %} + )).ReturnsAsync({% if method.responseModel and method.responseModel != 'any' and method.type != 'location' %}{{ spec.title | caseUcfirst }}.Models.{{ method.responseModel | caseUcfirst | overrideIdentifier }}.From(expectedResponse){% else %}expectedResponse{% endif %}); {%~ endif %}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig` around lines 193 - 201, The mock setup block has inconsistent leading spaces causing misaligned indentation; update the template so the lines inside the _mockClient.Setup(...) call (the It.IsAny<Func<...>>() conditional and the ,null branch) align with the other setup blocks — adjust the whitespace/indentation around the Twig tags (e.g., the {% if method.responseModel %} / {% else %} sections and the It.IsAny<Func<...>>() line) so the It.IsAny<Func<...>>() and ,null branches and the ).ReturnsAsync(...) line have the same indentation as the earlier blocks, keeping the existing conditionals (utils.resultType, method.responseModel, spec.title, overrideIdentifier) intact.templates/dotnet/Package.Tests/ClientTests.cs.twig (1)
32-48: Tests verify instantiation but not actual behavior.The
Constructor_WithSelfSignedandConstructor_WithHttpClienttests only assert that the client is not null. They don't verify that the constructor parameters actually take effect. This is a known limitation if the Client class doesn't expose these settings.Consider documenting this limitation or adding behavior-based assertions if the Client API permits (e.g., making a request that would fail without proper self-signed handling).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@templates/dotnet/Package.Tests/ClientTests.cs.twig` around lines 32 - 48, Update the two tests to verify that constructor parameters actually take effect: for Constructor_WithSelfSigned_EnablesSelfSigned, either assert a Client property or method that reflects selfSigned (e.g., Client.SelfSigned or behavior like sending a request to a self-signed server that succeeds) or, if the API lacks exposure, add a comment in the test explaining the limitation; for Constructor_WithHttpClient_UsesProvidedClient, assert that the Client instance is using the provided HttpClient (e.g., expose or check an internal/http property or perform a simple request that uses the provided HttpClient) or likewise document that the constructor does not expose the injected HttpClient. Ensure you reference the Client constructor and the two test methods (Constructor_WithSelfSigned_EnablesSelfSigned, Constructor_WithHttpClient_UsesProvidedClient) and either add explicit assertions or clear test comments describing why deeper verification isn’t possible.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@templates/dotnet/Package/Models/Model.cs.twig`:
- Around line 67-78: The generated ConvertTo<T>(Func<Dictionary<string, object>,
T> fromJson) methods produce duplicate signatures and an incorrect return type
for array properties; update the template so array properties generate a
distinct method (e.g., ConvertItemsTo<T>) that returns IEnumerable<T> and uses
Select(...). Specifically, in Model.cs.twig change the branch that matches
property.type == 'array' and property.sub_schema to emit
ConvertItemsTo<T>(Func<Dictionary<string, object>, T> fromJson) returning
IEnumerable<T> (and call {{ property.name | caseUcfirst | escapeKeyword
}}.Select(...)), and ensure you do not emit multiple methods with the same
signature per class (either by naming the array variant differently or by
generating a single shared method per class).
---
Duplicate comments:
In `@src/SDK/Language/DotNet.php`:
- Around line 601-606: The escapeCsString Twig filter currently only escapes
backslashes and double quotes causing newlines/tabs/etc. to break generated C#
strings; update the closure used in the new TwigFilter('escapeCsString', ...) so
it also replaces control characters with C# escape sequences (at minimum:
newline "\n" -> "\\n", carriage return "\r" -> "\\r", tab "\t" -> "\\t", null
"\0" -> "\\0", and optionally backspace "\b" and formfeed "\f"), while still
escaping backslashes and double quotes; implement this using a deterministic
replacement (e.g., str_replace or a single preg_replace_callback) within the
existing closure so all control characters are converted to their escaped
representations before returning the string.
In `@templates/dotnet/Package.Tests/ClientTests.cs.twig`:
- Around line 62-73: The template inserts raw header keys into C# string
literals causing syntax errors for quotes/backslashes; update each occurrence of
{{header.key}} used inside C# string contexts (e.g., the InlineData attributes
in the AddHeader_SetsCustomHeader test and the other occurrences noted around
lines with AddHeader usage) to use the escapeCsString filter so the values are
escaped for C# (apply escapeCsString to the InlineData values and the other
three spots where "{{header.key}}" is rendered).
In `@templates/dotnet/Package.Tests/Models/ModelTests.cs.twig`:
- Around line 71-72: The template currently treats 'integer' and 'number' the
same, causing integer literals to be used for double properties; update the
conditional in ModelTests.cs.twig so that when property.type == 'integer' you
Assert.Equal using the integer example/default (1) and when property.type ==
'number' you Assert.Equal using a floating literal (e.g., 1.0 or 1.0D) to match
C# double types; apply the same change to the second occurrence referenced
around lines 170-171. Use the existing helpers property.type,
property['x-example'], and property_name(definition, property) /
overrideProperty(definition.name) to locate and emit the correct literal per
type.
In `@templates/dotnet/Package.Tests/OperatorTests.cs.twig`:
- Line 193: Replace usages of JsonElement.ToString() with
JsonElement.GetString() in the OperatorTests assertions so string values are
extracted reliably; specifically update all assertions and comparisons that call
ToString() on elements inside op.Values (and any other JsonElement instances in
OperatorTests) to use GetString() instead (apply the same change to all
occurrences suggested in the review). Ensure you keep the existing Assert.Equal
calls and only swap the accessor method from ToString() to GetString() so the
tests assert the actual string content.
- Line 20: The test uses GetInt32() to assert numeric values in OperatorTests
(e.g., the Assert.Equal(1, ((JsonElement)op.Values[0]).GetInt32()) line) but
Operator methods accept double parameters; change those assertions to use
GetDouble() and compare against double literals (e.g., 1.0) to avoid
truncation/range issues and ensure consistency; update every numeric assertion
referencing op.Values[...] (lines noted in the review) to call GetDouble() and
use double expected values.
In `@templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig`:
- Around line 60-62: The template inserts property['x-example'] directly into C#
string literals for required string properties (inside the loop over
spec.definitions / property in ServiceTests.cs.twig), which can produce invalid
C# when that example contains quotes, backslashes, or newlines; update the
string branch (the elseif property.type == 'string' case) to wrap the example
with the existing escapeCsString filter (same as used at lines 104 and 165) so
examples are escaped before being placed into the C# literal, and ensure any
default('test') fallback is also processed by escapeCsString; reference the
string handling branch in the for loop and the escapeCsString utility used
elsewhere (and keep generate_sub_dict unchanged).
---
Nitpick comments:
In `@src/SDK/Language/DotNet.php`:
- Around line 736-738: The closure registered for the TwigFunction
'property_name' currently accepts (array $definition, array $property) but only
uses $property; remove the unused $definition parameter from the closure
signature so it becomes function (array $property) and call
$this->getPropertyName($property) inside it, and then update any templates that
call property_name(definition, property) to call property_name(property) to
match the new signature; ensure the change is applied where the TwigFunction is
constructed and any template usages are updated accordingly.
In `@templates/dotnet/Package.Tests/ClientTests.cs.twig`:
- Around line 32-48: Update the two tests to verify that constructor parameters
actually take effect: for Constructor_WithSelfSigned_EnablesSelfSigned, either
assert a Client property or method that reflects selfSigned (e.g.,
Client.SelfSigned or behavior like sending a request to a self-signed server
that succeeds) or, if the API lacks exposure, add a comment in the test
explaining the limitation; for Constructor_WithHttpClient_UsesProvidedClient,
assert that the Client instance is using the provided HttpClient (e.g., expose
or check an internal/http property or perform a simple request that uses the
provided HttpClient) or likewise document that the constructor does not expose
the injected HttpClient. Ensure you reference the Client constructor and the two
test methods (Constructor_WithSelfSigned_EnablesSelfSigned,
Constructor_WithHttpClient_UsesProvidedClient) and either add explicit
assertions or clear test comments describing why deeper verification isn’t
possible.
In `@templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig`:
- Around line 214-215: The test contains a no-op assertion Assert.True(true);
replace it with meaningful verification: either assert the expected return/state
values from the tested method or verify the mocked dependency received the
correct parameters (use mock.Verify(..., Times.Once()) or change It.IsAny<> to
It.Is<T>(p => p.Property == expected) to assert parameter values). If the method
truly has no observable effects and the mock interactions are already asserted
elsewhere, remove the empty test to avoid false confidence.
- Around line 193-201: The mock setup block has inconsistent leading spaces
causing misaligned indentation; update the template so the lines inside the
_mockClient.Setup(...) call (the It.IsAny<Func<...>>() conditional and the ,null
branch) align with the other setup blocks — adjust the whitespace/indentation
around the Twig tags (e.g., the {% if method.responseModel %} / {% else %}
sections and the It.IsAny<Func<...>>() line) so the It.IsAny<Func<...>>() and
,null branches and the ).ReturnsAsync(...) line have the same indentation as the
earlier blocks, keeping the existing conditionals (utils.resultType,
method.responseModel, spec.title, overrideIdentifier) intact.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
.github/workflows/sdk-build-validation.ymlsrc/SDK/Language/DotNet.phptemplates/dotnet/Package.Tests/ClientTests.cs.twigtemplates/dotnet/Package.Tests/Models/ModelTests.cs.twigtemplates/dotnet/Package.Tests/OperatorTests.cs.twigtemplates/dotnet/Package.Tests/PermissionTests.cs.twigtemplates/dotnet/Package.Tests/QueryTests.cs.twigtemplates/dotnet/Package.Tests/RoleTests.cs.twigtemplates/dotnet/Package.Tests/Services/ServiceTests.cs.twigtemplates/dotnet/Package.Tests/UploadProgressTests.cs.twigtemplates/dotnet/Package/Exception.cs.twigtemplates/dotnet/Package/Extensions/Extensions.cs.twigtemplates/dotnet/Package/Models/Model.cs.twigtemplates/dotnet/Package/Models/RequestModel.cs.twigtemplates/dotnet/Package/Query.cs.twigtemplates/dotnet/base/utils.twig
🚧 Files skipped from review as they are similar to previous changes (7)
- templates/dotnet/base/utils.twig
- templates/dotnet/Package.Tests/UploadProgressTests.cs.twig
- templates/dotnet/Package/Extensions/Extensions.cs.twig
- templates/dotnet/Package/Exception.cs.twig
- templates/dotnet/Package.Tests/PermissionTests.cs.twig
- templates/dotnet/Package.Tests/RoleTests.cs.twig
- .github/workflows/sdk-build-validation.yml
What does this PR do?
based on #1138
This PR adds comprehensive testing infrastructure for the .NET SDK generator, including test templates and necessary utilities.
Also added 'Run Tests' step to CI workflow in sdk-build-validation.yml
Added Files and Test Types:
Core Infrastructure:
Tests.csproj.twig- .NET test project template.gitignore- Build artifacts exclusion for testsSDK Common Tests:
ClientTests.cs.twig- Client tests (constructors, configuration, headers, serialization)IDTests.cs.twig- ID generation testsPermissionTests.cs.twig- Permission testsRoleTests.cs.twig- Role testsQueryTests.cs.twig- Query testsExceptionTests.cs.twig- Exception testsUploadProgressTests.cs.twig- Upload progress testsModel and Type Tests:
InputFileTests.cs.twig- Input file testsModelTests.cs.twig- Model tests (constructors, serialization, deserialization)EnumTests.cs.twig- Enum testsConverter Tests:
ObjectToInferredTypesConverterTests.cs.twig- JSON type converter testsValueClassConverterTests.cs.twig- Value class converter testsService Tests:
ServiceTests.cs.twig- API service tests (method calls, parameters, mocking)New Utilities:
escapeCsString- String escaping for C#test_item_type- Array element type determination for testsCode Changes:
src/SDK/Language/DotNet.php- Added test file generation templates and utility functionsThis PR generates tests for each model definition, enum, and service in the API specification, providing comprehensive test coverage for the generated .NET SDK.
Test Plan
Related PRs and Issues
Have you read the Contributing Guidelines on issues?
Yes
Summary by CodeRabbit