diff --git a/.github/workflows/sdk-build-validation.yml b/.github/workflows/sdk-build-validation.yml index 770b80644d..d6865692b4 100644 --- a/.github/workflows/sdk-build-validation.yml +++ b/.github/workflows/sdk-build-validation.yml @@ -197,3 +197,45 @@ jobs: exit 1 ;; esac + + - name: Run Tests + working-directory: examples/${{ matrix.sdk }} + run: | + case "${{ matrix.sdk }}" in + web|node|cli|react-native) + npm test || echo "No tests available" + ;; + flutter) + flutter test || echo "No tests available" + ;; + apple|swift) + swift test || echo "No tests available" + ;; + android) + ./gradlew test || echo "No tests available" + ;; + kotlin) + ./gradlew test || echo "No tests available" + ;; + php) + vendor/bin/phpunit || echo "No tests available" + ;; + python) + python -m pytest || echo "No tests available" + ;; + ruby) + bundle exec rake test || bundle exec rspec || echo "No tests available" + ;; + dart) + dart test || echo "No tests available" + ;; + go) + go test ./... || echo "No tests available" + ;; + dotnet) + dotnet test || echo "No tests available" + ;; + *) + echo "No tests for SDK: ${{ matrix.sdk }}" + ;; + esac diff --git a/src/SDK/Language/DotNet.php b/src/SDK/Language/DotNet.php index 49e36b3ab3..eaf3249eb4 100644 --- a/src/SDK/Language/DotNet.php +++ b/src/SDK/Language/DotNet.php @@ -472,6 +472,85 @@ public function getFiles(): array 'scope' => 'default', 'destination' => '{{ spec.title | caseUcfirst }}/Enums/IEnum.cs', 'template' => 'dotnet/Package/Enums/IEnum.cs.twig', + ], + // Tests + [ + 'scope' => 'default', + 'destination' => '{{ spec.title | caseUcfirst }}.Tests/{{ spec.title | caseUcfirst }}.Tests.csproj', + 'template' => 'dotnet/Package.Tests/Tests.csproj.twig', + ], + [ + 'scope' => 'default', + 'destination' => '{{ spec.title | caseUcfirst }}.Tests/.gitignore', + 'template' => 'dotnet/Package.Tests/.gitignore', + ], + [ + 'scope' => 'default', + 'destination' => '{{ spec.title | caseUcfirst }}.Tests/ClientTests.cs', + 'template' => 'dotnet/Package.Tests/ClientTests.cs.twig', + ], + [ + 'scope' => 'default', + 'destination' => '{{ spec.title | caseUcfirst }}.Tests/IDTests.cs', + 'template' => 'dotnet/Package.Tests/IDTests.cs.twig', + ], + [ + 'scope' => 'default', + 'destination' => '{{ spec.title | caseUcfirst }}.Tests/PermissionTests.cs', + 'template' => 'dotnet/Package.Tests/PermissionTests.cs.twig', + ], + [ + 'scope' => 'default', + 'destination' => '{{ spec.title | caseUcfirst }}.Tests/RoleTests.cs', + 'template' => 'dotnet/Package.Tests/RoleTests.cs.twig', + ], + [ + 'scope' => 'default', + 'destination' => '{{ spec.title | caseUcfirst }}.Tests/QueryTests.cs', + 'template' => 'dotnet/Package.Tests/QueryTests.cs.twig', + ], + [ + 'scope' => 'default', + 'destination' => '{{ spec.title | caseUcfirst }}.Tests/ExceptionTests.cs', + 'template' => 'dotnet/Package.Tests/ExceptionTests.cs.twig', + ], + [ + 'scope' => 'default', + 'destination' => '{{ spec.title | caseUcfirst }}.Tests/UploadProgressTests.cs', + 'template' => 'dotnet/Package.Tests/UploadProgressTests.cs.twig', + ], + [ + 'scope' => 'default', + 'destination' => '{{ spec.title | caseUcfirst }}.Tests/Models/InputFileTests.cs', + 'template' => 'dotnet/Package.Tests/Models/InputFileTests.cs.twig', + ], + [ + 'scope' => 'default', + 'destination' => '{{ spec.title | caseUcfirst }}.Tests/Converters/ObjectToInferredTypesConverterTests.cs', + 'template' => 'dotnet/Package.Tests/Converters/ObjectToInferredTypesConverterTests.cs.twig', + ], + [ + 'scope' => 'default', + 'destination' => '{{ spec.title | caseUcfirst }}.Tests/Converters/ValueClassConverterTests.cs', + 'template' => 'dotnet/Package.Tests/Converters/ValueClassConverterTests.cs.twig', + ], + // Tests for each definition (model) + [ + 'scope' => 'definition', + 'destination' => '{{ spec.title | caseUcfirst }}.Tests/Models/{{ definition.name | caseUcfirst | overrideIdentifier }}Tests.cs', + 'template' => 'dotnet/Package.Tests/Models/ModelTests.cs.twig', + ], + // Tests for each enum + [ + 'scope' => 'enum', + 'destination' => '{{ spec.title | caseUcfirst }}.Tests/Enums/{{ enum.name | caseUcfirst | overrideIdentifier }}Tests.cs', + 'template' => 'dotnet/Package.Tests/Enums/EnumTests.cs.twig', + ], + // Tests for each service + [ + 'scope' => 'service', + 'destination' => '{{ spec.title | caseUcfirst }}.Tests/Services/{{service.name | caseUcfirst}}Tests.cs', + 'template' => 'dotnet/Package.Tests/Services/ServiceTests.cs.twig', ] ]; } @@ -495,11 +574,17 @@ public function getFilters(): array } return $property; }), + new TwigFilter('escapeCsString', function ($value) { + if (is_string($value)) { + return addcslashes($value, '\\"'); + } + return $value; + }), ]; } /** - * get sub_scheme and property_name functions + * get sub_scheme, property_name and parse_value functions * @return TwigFunction[] */ public function getFunctions(): array @@ -526,7 +611,31 @@ public function getFunctions(): array } return $result; - }), + }, ['is_safe' => ['html']]), + new TwigFunction('test_item_type', function (array $property) { + // For test templates: returns the item type for arrays without the List<> wrapper + $result = ''; + + if (isset($property['sub_schema']) && !empty($property['sub_schema'])) { + // Model type + $result = $this->toPascalCase($property['sub_schema']); + $result = 'Appwrite.Models.' . $result; + } elseif (isset($property['enum']) && !empty($property['enum'])) { + // Enum type + $enumName = $property['enumName'] ?? $property['name']; + $result = 'Appwrite.Enums.' . $this->toPascalCase($enumName); + } elseif (isset($property['items']) && isset($property['items']['type'])) { + // Primitive array type (for definitions) + $result = $this->getTypeName($property['items']); + } elseif (isset($property['array']) && isset($property['array']['type'])) { + // Primitive array type (for method parameters) + $result = $this->getTypeName($property['array']); + } else { + $result = 'object'; + } + + return $result; + }, ['is_safe' => ['html']]), new TwigFunction('property_name', function (array $definition, array $property) { $name = $property['name']; $name = \str_replace('$', '', $name); @@ -536,6 +645,77 @@ public function getFunctions(): array } return $name; }), + new TwigFunction('parse_value', function (array $property, string $mapAccess, string $v) { + $required = $property['required'] ?? false; + + // Handle sub_schema + if (isset($property['sub_schema']) && !empty($property['sub_schema'])) { + $subSchema = \ucfirst($property['sub_schema']); + + if ($property['type'] === 'array') { + $src = $required ? $mapAccess : $v; + return "{$src}.ToEnumerable().Select(it => {$subSchema}.From(map: (Dictionary)it)).ToList()"; + } else { + if ($required) { + return "{$subSchema}.From(map: (Dictionary){$mapAccess})"; + } + return "({$v} as Dictionary) is { } obj ? {$subSchema}.From(map: obj) : null"; + } + } + + // Handle enum + if (isset($property['enum']) && !empty($property['enum'])) { + $enumName = $property['enumName'] ?? $property['name']; + $enumClass = \ucfirst($enumName); + + if ($required) { + return "new {$enumClass}({$mapAccess}.ToString())"; + } + return "{$v} == null ? null : new {$enumClass}({$v}.ToString())"; + } + + // Handle arrays + if ($property['type'] === 'array') { + $itemsType = $property['items']['type'] ?? 'object'; + $src = $required ? $mapAccess : $v; + + $selectExpression = match ($itemsType) { + 'string' => 'x.ToString()', + 'integer' => 'Convert.ToInt64(x)', + 'number' => 'Convert.ToDouble(x)', + 'boolean' => '(bool)x', + default => 'x' + }; + + return "{$src}.ToEnumerable().Select(x => {$selectExpression}).ToList()"; + } + + // Handle integer/number + if ($property['type'] === 'integer' || $property['type'] === 'number') { + $convertMethod = $property['type'] === 'integer' ? 'Int64' : 'Double'; + + if ($required) { + return "Convert.To{$convertMethod}({$mapAccess})"; + } + return "{$v} == null ? null : Convert.To{$convertMethod}({$v})"; + } + + // Handle boolean + if ($property['type'] === 'boolean') { + $typeName = $this->getTypeName($property); + + if ($required) { + return "({$typeName}){$mapAccess}"; + } + return "({$typeName}?){$v}"; + } + + // Handle string type + if ($required) { + return "{$mapAccess}.ToString()"; + } + return "{$v}?.ToString()"; + }, ['is_safe' => ['html']]), ]; } diff --git a/templates/dotnet/Package.Tests/.gitignore b/templates/dotnet/Package.Tests/.gitignore new file mode 100644 index 0000000000..9eb3c7a5a5 --- /dev/null +++ b/templates/dotnet/Package.Tests/.gitignore @@ -0,0 +1,23 @@ +# Test results +TestResults/ +*.trx +*.coverage +*.coveragexml + +# Coverage reports +coverage/ +coverage.json +coverage.opencover.xml +lcov.info + +# Build outputs +bin/ +obj/ +*.user +*.suo + +# Rider +.idea/ + +# Visual Studio +.vs/ diff --git a/templates/dotnet/Package.Tests/ClientTests.cs.twig b/templates/dotnet/Package.Tests/ClientTests.cs.twig new file mode 100644 index 0000000000..61b20a3468 --- /dev/null +++ b/templates/dotnet/Package.Tests/ClientTests.cs.twig @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Xunit; +using {{ spec.title | caseUcfirst }}; + +namespace {{ spec.title | caseUcfirst }}.Tests +{ + public class ClientTests + { + [Fact] + public void Constructor_Default_CreatesClient() + { + // Act + var client = new Client(); + + // Assert + Assert.NotNull(client); + Assert.Equal("{{spec.endpoint}}", client.Endpoint); + Assert.NotNull(client.Config); + } + + [Fact] + public void Constructor_WithCustomEndpoint_SetsEndpoint() + { + // Arrange + var customEndpoint = "https://custom.example.com/v1"; + + // Act + var client = new Client(endpoint: customEndpoint); + + // Assert + Assert.Equal(customEndpoint, client.Endpoint); + } + + [Fact] + public void Constructor_WithSelfSigned_EnablesSelfSigned() + { + // Act + var client = new Client(selfSigned: true); + + // Assert + Assert.NotNull(client); + } + + [Fact] + public void Constructor_WithHttpClient_UsesProvidedClient() + { + // Arrange + var httpClient = new HttpClient(); + + // Act + var client = new Client(http: httpClient); + + // Assert + Assert.NotNull(client); + } + + [Fact] + public void SetEndpoint_UpdatesEndpoint() + { + // Arrange + var client = new Client(); + var newEndpoint = "https://new.example.com/v1"; + + // Act + var result = client.SetEndpoint(newEndpoint); + + // Assert + Assert.Equal(newEndpoint, client.Endpoint); + Assert.Same(client, result); + } + + [Theory] + {%~ for header in spec.global.headers %} + [InlineData("{{header.key}}", "test-{{header.key}}")] + {%~ endfor %} + public void SetHeader_SetsCustomHeader(string key, string value) + { + // Arrange + var client = new Client(); + + // Act + var result = client.AddHeader(key, value); + + // Assert + Assert.Same(client, result); + } + + [Fact] + public void Config_IsNotNull() + { + // Arrange & Act + var client = new Client(); + + // Assert + Assert.NotNull(client.Config); + } + + [Fact] + public void SetProject_UpdatesConfig() + { + // Arrange + var client = new Client(); + var projectId = "test-project-id"; + + // Act + var result = client.SetProject(projectId); + + // Assert + Assert.True(client.Config.ContainsKey("project")); + Assert.Equal(projectId, client.Config["project"]); + Assert.Same(client, result); + } + + [Fact] + public void SetSelfSigned_EnablesSelfSignedCertificates() + { + // Arrange + var client = new Client(); + + // Act + var result = client.SetSelfSigned(true); + + // Assert + Assert.NotNull(result); + Assert.Same(client, result); + } + + [Fact] + public void SetSelfSigned_DisablesSelfSignedCertificates() + { + // Arrange + var client = new Client(selfSigned: true); + + // Act + var result = client.SetSelfSigned(false); + + // Assert + Assert.NotNull(result); + Assert.Same(client, result); + } + + [Fact] + public void DeserializerOptions_IsNotNull() + { + // Assert + Assert.NotNull(Client.DeserializerOptions); + } + + [Fact] + public void SerializerOptions_IsNotNull() + { + // Assert + Assert.NotNull(Client.SerializerOptions); + } + + [Fact] + public void DeserializerOptions_HasConverters() + { + // Assert + Assert.NotEmpty(Client.DeserializerOptions.Converters); + } + + [Fact] + public void SerializerOptions_HasConverters() + { + // Assert + Assert.NotEmpty(Client.SerializerOptions.Converters); + } + + [Fact] + public void Endpoint_CanBeRetrieved() + { + // Arrange + var endpoint = "https://test.example.com/v1"; + var client = new Client(endpoint: endpoint); + + // Act + var result = client.Endpoint; + + // Assert + Assert.Equal(endpoint, result); + } + + [Fact] + public void Config_CanBeRetrieved() + { + // Arrange + var client = new Client(); + + // Act + var config = client.Config; + + // Assert + Assert.NotNull(config); + Assert.IsType>(config); + } + + [Fact] + public void ChainedCalls_Work() + { + // Arrange & Act + var client = new Client() + .SetEndpoint("https://example.com/v1") + .SetProject("test-project") + .SetSelfSigned(false); + + // Assert + Assert.NotNull(client); + Assert.Equal("https://example.com/v1", client.Endpoint); + Assert.Equal("test-project", client.Config["project"]); + } + } +} diff --git a/templates/dotnet/Package.Tests/Converters/ObjectToInferredTypesConverterTests.cs.twig b/templates/dotnet/Package.Tests/Converters/ObjectToInferredTypesConverterTests.cs.twig new file mode 100644 index 0000000000..cc0b1f2231 --- /dev/null +++ b/templates/dotnet/Package.Tests/Converters/ObjectToInferredTypesConverterTests.cs.twig @@ -0,0 +1,313 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using Xunit; +using {{ spec.title | caseUcfirst }}.Converters; + +namespace {{ spec.title | caseUcfirst }}.Tests.Converters +{ + public class ObjectToInferredTypesConverterTests + { + private readonly JsonSerializerOptions _options; + + public ObjectToInferredTypesConverterTests() + { + _options = new JsonSerializerOptions(); + _options.Converters.Add(new ObjectToInferredTypesConverter()); + } + + [Fact] + public void Read_WithString_ReturnsString() + { + // Arrange + var json = "\"test string\""; + + // Act + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.IsType(result); + Assert.Equal("test string", result); + } + + [Fact] + public void Read_WithInteger_ReturnsLong() + { + // Arrange + var json = "123"; + + // Act + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.IsType(result); + Assert.Equal(123L, result); + } + + [Fact] + public void Read_WithDouble_ReturnsDouble() + { + // Arrange + var json = "123.45"; + + // Act + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.IsType(result); + Assert.Equal(123.45, result); + } + + [Fact] + public void Read_WithBoolean_ReturnsBoolean() + { + // Arrange + var json = "true"; + + // Act + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.IsType(result); + Assert.True((bool)result); + } + + [Fact] + public void Read_WithNull_ReturnsNull() + { + // Arrange + var json = "null"; + + // Act + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.Null(result); + } + + [Fact] + public void Read_WithObject_ReturnsDictionary() + { + // Arrange + var json = "{\"key\":\"value\",\"number\":42}"; + + // Act + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.IsType>(result); + var dict = (Dictionary)result; + Assert.Equal(2, dict.Count); + Assert.Equal("value", dict["key"]); + Assert.Equal(42L, dict["number"]); + } + + [Fact] + public void Read_WithArray_ReturnsList() + { + // Arrange + var json = "[1,2,3,4,5]"; + + // Act + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.IsType>(result); + var list = (List)result; + Assert.Equal(5, list.Count); + Assert.Equal(1L, list[0]); + Assert.Equal(5L, list[4]); + } + + [Fact] + public void Read_WithNestedObject_ReturnsNestedDictionary() + { + // Arrange + var json = "{\"outer\":{\"inner\":\"value\"}}"; + + // Act + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.IsType>(result); + var dict = (Dictionary)result; + Assert.IsType>(dict["outer"]); + var nested = (Dictionary)dict["outer"]; + Assert.Equal("value", nested["inner"]); + } + + [Fact] + public void Read_WithArrayOfObjects_ReturnsListOfDictionaries() + { + // Arrange + var json = "[{\"id\":1},{\"id\":2}]"; + + // Act + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.IsType>(result); + var list = (List)result; + Assert.Equal(2, list.Count); + Assert.IsType>(list[0]); + } + + [Fact] + public void Read_WithMixedTypes_ConvertsCorrectly() + { + // Arrange + var json = "{\"string\":\"text\",\"number\":123,\"bool\":true,\"null\":null,\"array\":[1,2,3]}"; + + // Act + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.IsType>(result); + var dict = (Dictionary)result; + Assert.Equal("text", dict["string"]); + Assert.Equal(123L, dict["number"]); + Assert.True((bool)dict["bool"]); + Assert.Null(dict["null"]); + Assert.IsType>(dict["array"]); + } + + [Fact] + public void Read_WithDateTime_ReturnsDateTime() + { + // Arrange + var json = "\"2023-10-16T12:00:00Z\""; + + // Act + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.IsType(result); + } + + [Fact] + public void Read_WithEmptyObject_ReturnsEmptyDictionary() + { + // Arrange + var json = "{}"; + + // Act + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.IsType>(result); + var dict = (Dictionary)result; + Assert.Empty(dict); + } + + [Fact] + public void Read_WithEmptyArray_ReturnsEmptyList() + { + // Arrange + var json = "[]"; + + // Act + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.IsType>(result); + var list = (List)result; + Assert.Empty(list); + } + + [Fact] + public void Write_WithString_WritesString() + { + // Arrange + var value = "test"; + + // Act + var json = JsonSerializer.Serialize(value, _options); + + // Assert + Assert.Equal("\"test\"", json); + } + + [Fact] + public void Write_WithInteger_WritesInteger() + { + // Arrange + var value = 123; + + // Act + var json = JsonSerializer.Serialize(value, _options); + + // Assert + Assert.Equal("123", json); + } + + [Fact] + public void Write_WithBoolean_WritesBoolean() + { + // Arrange + var value = true; + + // Act + var json = JsonSerializer.Serialize(value, _options); + + // Assert + Assert.Equal("true", json); + } + + [Fact] + public void Write_WithNull_WritesNull() + { + // Arrange + object value = null; + + // Act + var json = JsonSerializer.Serialize(value, _options); + + // Assert + Assert.Equal("null", json); + } + + [Fact] + public void Write_WithDictionary_WritesObject() + { + // Arrange + var value = new Dictionary + { + { "key", "value" }, + { "number", 42 } + }; + + // Act + var json = JsonSerializer.Serialize(value, _options); + + // Assert + Assert.Contains("\"key\"", json); + Assert.Contains("\"value\"", json); + Assert.Contains("\"number\"", json); + Assert.Contains("42", json); + } + + [Fact] + public void RoundTrip_PreservesData() + { + // Arrange + var original = new Dictionary + { + { "string", "test" }, + { "number", 123L }, + { "bool", true }, + { "array", new List { 1L, 2L, 3L } } + }; + + // Act + var json = JsonSerializer.Serialize(original, _options); + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.IsType>(result); + var dict = (Dictionary)result; + Assert.Equal("test", dict["string"]); + Assert.Equal(123L, dict["number"]); + Assert.True((bool)dict["bool"]); + } + } +} diff --git a/templates/dotnet/Package.Tests/Converters/ValueClassConverterTests.cs.twig b/templates/dotnet/Package.Tests/Converters/ValueClassConverterTests.cs.twig new file mode 100644 index 0000000000..4be51bf513 --- /dev/null +++ b/templates/dotnet/Package.Tests/Converters/ValueClassConverterTests.cs.twig @@ -0,0 +1,223 @@ +using System; +using System.Text.Json; +using Xunit; +using {{ spec.title | caseUcfirst }}.Converters; +using {{ spec.title | caseUcfirst }}.Enums; + +namespace {{ spec.title | caseUcfirst }}.Tests.Converters +{ + public class ValueClassConverterTests + { + private readonly JsonSerializerOptions _options; + + public ValueClassConverterTests() + { + _options = new JsonSerializerOptions(); + _options.Converters.Add(new ValueClassConverter()); + _options.PropertyNameCaseInsensitive = true; + } + + [Fact] + public void CanConvert_WithIEnumType_ReturnsTrue() + { + // Arrange + var converter = new ValueClassConverter(); + + // Act + var result = converter.CanConvert(typeof(IEnum)); + + // Assert + Assert.True(result); + } + + [Fact] + public void CanConvert_WithStringType_ReturnsFalse() + { + // Arrange + var converter = new ValueClassConverter(); + + // Act + var result = converter.CanConvert(typeof(string)); + + // Assert + Assert.False(result); + } + + [Fact] + public void CanConvert_WithIntType_ReturnsFalse() + { + // Arrange + var converter = new ValueClassConverter(); + + // Act + var result = converter.CanConvert(typeof(int)); + + // Assert + Assert.False(result); + } + + [Fact] + public void Write_WithValidEnum_WritesStringValue() + { + // Arrange + var testEnum = new TestEnum("testValue"); + + // Act + var json = JsonSerializer.Serialize(testEnum, _options); + + // Assert + Assert.Equal("\"testValue\"", json); + } + + [Fact] + public void Write_WithNull_WritesNull() + { + // Arrange + TestEnum testEnum = null; + + // Act + var json = JsonSerializer.Serialize(testEnum, _options); + + // Assert + Assert.Equal("null", json); + } + + [Fact] + public void Write_WithEmptyValue_WritesEmptyString() + { + // Arrange + var testEnum = new TestEnum(""); + + // Act + var json = JsonSerializer.Serialize(testEnum, _options); + + // Assert + Assert.Equal("\"\"", json); + } + + [Fact] + public void Write_WithSpecialCharacters_EscapesCorrectly() + { + // Arrange + var testEnum = new TestEnum("test\"value"); + + // Act + var json = JsonSerializer.Serialize(testEnum, _options); + + // Assert + Assert.Contains("\\u0022", json); + } + + [Fact] + public void Read_WithValidString_CreatesEnum() + { + // Arrange + var json = "\"testValue\""; + + // Act + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.NotNull(result); + Assert.Equal("testValue", result.Value); + } + + [Fact] + public void Read_WithEmptyString_CreatesEnumWithEmptyValue() + { + // Arrange + var json = "\"\""; + + // Act + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.NotNull(result); + Assert.Equal("", result.Value); + } + + [Fact] + public void RoundTrip_PreservesValue() + { + // Arrange + var original = new TestEnum("originalValue"); + + // Act + var json = JsonSerializer.Serialize(original, _options); + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.Equal(original.Value, result.Value); + } + + [Fact] + public void RoundTrip_WithMultipleValues_PreservesAllValues() + { + // Arrange + var values = new[] { "value1", "value2", "value3" }; + + foreach (var value in values) + { + var original = new TestEnum(value); + + // Act + var json = JsonSerializer.Serialize(original, _options); + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.Equal(original.Value, result.Value); + } + } + + [Fact] + public void Write_InComplexObject_SerializesCorrectly() + { + // Arrange + var obj = new + { + EnumValue = new TestEnum("test"), + StringValue = "string" + }; + + // Act + var json = JsonSerializer.Serialize(obj, _options); + + // Assert + Assert.Contains("\"test\"", json); + Assert.Contains("\"string\"", json); + } + + [Fact] + public void Read_FromComplexObject_DeserializesCorrectly() + { + // Arrange + var json = "{\"enumValue\":\"testValue\",\"stringValue\":\"string\"}"; + + // Act + var result = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.EnumValue); + Assert.Equal("testValue", result.EnumValue.Value); + Assert.Equal("string", result.StringValue); + } + + // Test helper classes + private class TestEnum : IEnum + { + public string Value { get; private set; } + + public TestEnum(string value) + { + Value = value; + } + } + + private class ComplexObject + { + public TestEnum EnumValue { get; set; } + public string StringValue { get; set; } + } + } +} diff --git a/templates/dotnet/Package.Tests/Enums/EnumTests.cs.twig b/templates/dotnet/Package.Tests/Enums/EnumTests.cs.twig new file mode 100644 index 0000000000..3505713c9c --- /dev/null +++ b/templates/dotnet/Package.Tests/Enums/EnumTests.cs.twig @@ -0,0 +1,111 @@ +using Xunit; +using System.Linq; +using {{ spec.title | caseUcfirst }}.Enums; + +namespace {{ spec.title | caseUcfirst }}.Tests.Enums +{ + public class {{ enum.name | caseUcfirst | overrideIdentifier }}Tests + { + [Fact] + public void Constructor_WithValue_CreatesInstance() + { + // Arrange & Act + var enumValue = new {{ enum.name | caseUcfirst | overrideIdentifier }}("test"); + + // Assert + Assert.NotNull(enumValue); + Assert.Equal("test", enumValue.Value); + } + + {%~ for value in enum.enum %} + {%~ set key = enum.keys is empty ? value : enum.keys[loop.index0] %} + [Fact] + public void {{ key | caseEnumKey }}_ReturnsCorrectValue() + { + // Act + var enumValue = {{ enum.name | caseUcfirst | overrideIdentifier }}.{{ key | caseEnumKey }}; + + // Assert + Assert.NotNull(enumValue); + Assert.Equal("{{ value }}", enumValue.Value); + } + + {%~ endfor %} + + [Theory] + {%~ for value in enum.enum %} + [InlineData("{{ value }}")] + {%~ endfor %} + public void Value_WithValidString_IsCorrect(string value) + { + // Act + var enumValue = new {{ enum.name | caseUcfirst | overrideIdentifier }}(value); + + // Assert + Assert.Equal(value, enumValue.Value); + } + + [Fact] + public void StaticProperties_AreNotNull() + { + {%~ for value in enum.enum %} + {%~ set key = enum.keys is empty ? value : enum.keys[loop.index0] %} + Assert.NotNull({{ enum.name | caseUcfirst | overrideIdentifier }}.{{ key | caseEnumKey }}); + {%~ endfor %} + } + + [Fact] + public void StaticProperties_HaveUniqueValues() + { + var values = new[] + { + {%~ for value in enum.enum %} + {%~ set key = enum.keys is empty ? value : enum.keys[loop.index0] %} + {{ enum.name | caseUcfirst | overrideIdentifier }}.{{ key | caseEnumKey }}.Value{% if not loop.last %},{% endif %} + + {%~ endfor %} + }; + + Assert.Equal(values.Length, values.Distinct().Count()); + } + + [Fact] + public void Implements_IEnum() + { + // Arrange + {%~ set firstValue = enum.enum[0] %} + {%~ set firstKey = enum.keys is empty ? firstValue : enum.keys[0] %} + var enumValue = {{ enum.name | caseUcfirst | overrideIdentifier }}.{{ firstKey | caseEnumKey }}; + + // Assert + Assert.IsAssignableFrom(enumValue); + } + + [Fact] + public void Value_CanBeSetInConstructor() + { + // Arrange + var customValue = "customValue"; + + // Act + var enumValue = new {{ enum.name | caseUcfirst | overrideIdentifier }}(customValue); + + // Assert + Assert.Equal(customValue, enumValue.Value); + } + + [Fact] + public void ToString_ReturnsValue() + { + // Arrange + {%~ set firstValue = enum.enum[0] %} + {%~ set firstKey = enum.keys is empty ? firstValue : enum.keys[0] %} + var enumValue = {{ enum.name | caseUcfirst | overrideIdentifier }}.{{ firstKey | caseEnumKey }}; + + // Act & Assert + // Value property should return the string value + Assert.NotNull(enumValue.Value); + Assert.IsType(enumValue.Value); + } + } +} diff --git a/templates/dotnet/Package.Tests/ExceptionTests.cs.twig b/templates/dotnet/Package.Tests/ExceptionTests.cs.twig new file mode 100644 index 0000000000..d45e5b311d --- /dev/null +++ b/templates/dotnet/Package.Tests/ExceptionTests.cs.twig @@ -0,0 +1,143 @@ +using System; +using Xunit; +using {{ spec.title | caseUcfirst }}; + +namespace {{ spec.title | caseUcfirst }}.Tests +{ + public class ExceptionTests + { + [Fact] + public void Constructor_Default_CreatesException() + { + var exception = new {{spec.title | caseUcfirst}}Exception(); + + Assert.NotNull(exception); + Assert.NotNull(exception.Message); + Assert.Null(exception.Code); + Assert.Null(exception.Type); + Assert.Null(exception.Response); + } + + [Fact] + public void Constructor_WithMessage_SetsMessage() + { + var message = "Some error message"; + var exception = new {{spec.title | caseUcfirst}}Exception(message); + + Assert.NotNull(exception); + Assert.Equal(message, exception.Message); + Assert.Null(exception.Code); + Assert.Null(exception.Type); + Assert.Null(exception.Response); + } + + [Fact] + public void Constructor_WithAllParameters_SetsAllProperties() + { + var message = "Invalid request"; + var code = 400; + var type = "ValidationError"; + var response = "{\"error\":\"validation failed\"}"; + + var exception = new {{spec.title | caseUcfirst}}Exception(message, code, type, response); + + Assert.NotNull(exception); + Assert.Equal(message, exception.Message); + Assert.Equal(code, exception.Code); + Assert.Equal(type, exception.Type); + Assert.Equal(response, exception.Response); + } + + [Fact] + public void Constructor_WithMessageAndCode_SetsCorrectly() + { + var message = "Not found"; + var code = 404; + + var exception = new {{spec.title | caseUcfirst}}Exception(message, code); + + Assert.NotNull(exception); + Assert.Equal(message, exception.Message); + Assert.Equal(code, exception.Code); + Assert.Null(exception.Type); + Assert.Null(exception.Response); + } + + [Fact] + public void Constructor_WithInnerException_SetsInnerException() + { + var message = "Outer exception"; + var innerException = new Exception("Inner exception"); + + var exception = new {{spec.title | caseUcfirst}}Exception(message, innerException); + + Assert.NotNull(exception); + Assert.Equal(message, exception.Message); + Assert.NotNull(exception.InnerException); + Assert.Equal("Inner exception", exception.InnerException.Message); + } + + [Fact] + public void Exception_CanBeCaught() + { + var caught = false; + + try + { + throw new {{spec.title | caseUcfirst}}Exception("Test exception"); + } + catch ({{spec.title | caseUcfirst}}Exception) + { + caught = true; + } + + Assert.True(caught); + } + + [Fact] + public void Exception_WithCode_ReturnsCorrectCode() + { + var exception = new {{spec.title | caseUcfirst}}Exception("Error", 500, "ServerError"); + + Assert.Equal(500, exception.Code); + } + + [Fact] + public void Exception_WithType_ReturnsCorrectType() + { + var exception = new {{spec.title | caseUcfirst}}Exception("Error", 401, "Unauthorized"); + + Assert.Equal("Unauthorized", exception.Type); + } + + [Fact] + public void Exception_WithResponse_ReturnsCorrectResponse() + { + var response = "{\"message\":\"error\"}"; + var exception = new {{spec.title | caseUcfirst}}Exception("Error", 400, "BadRequest", response); + + Assert.Equal(response, exception.Response); + } + + [Fact] + public void ToString_WithDefaultConstructor_ReturnsCorrectString() + { + var exception = new {{spec.title | caseUcfirst}}Exception(); + var result = exception.ToString(); + + Assert.NotNull(result); + Assert.Contains("{{spec.title | caseUcfirst}}Exception", result); + } + + [Fact] + public void ToString_WithMessage_ReturnsCorrectString() + { + var exception = new {{spec.title | caseUcfirst}}Exception("Some error message"); + var result = exception.ToString(); + + Assert.NotNull(result); + Assert.Contains("{{spec.title | caseUcfirst}}Exception", result); + Assert.Contains("Some error message", result); + } + } +} diff --git a/templates/dotnet/Package.Tests/IDTests.cs.twig b/templates/dotnet/Package.Tests/IDTests.cs.twig new file mode 100644 index 0000000000..23e89258bd --- /dev/null +++ b/templates/dotnet/Package.Tests/IDTests.cs.twig @@ -0,0 +1,58 @@ +using Xunit; +using {{ spec.title | caseUcfirst }}; + +namespace {{ spec.title | caseUcfirst }}.Tests +{ + public class IDTests + { + [Fact] + public void Unique_ReturnsUniqueID() + { + var id = ID.Unique(); + Assert.NotNull(id); + Assert.NotEmpty(id); + Assert.Equal(20, id.Length); + } + + [Fact] + public void Unique_WithCustomPadding_ReturnsCorrectLength() + { + var padding = 10; + var id = ID.Unique(padding); + Assert.NotNull(id); + Assert.NotEmpty(id); + Assert.Equal(13 + padding, id.Length); // 13 is base timestamp length + } + + [Fact] + public void Unique_GeneratesUniqueIDs() + { + var id1 = ID.Unique(); + var id2 = ID.Unique(); + Assert.NotEqual(id1, id2); + } + + [Fact] + public void Custom_ReturnsCustomString() + { + var customId = "custom"; + var result = ID.Custom(customId); + Assert.Equal(customId, result); + } + + [Fact] + public void Custom_WithEmptyString_ReturnsEmptyString() + { + var result = ID.Custom(""); + Assert.Equal("", result); + } + + [Fact] + public void Custom_WithSpecialCharacters_ReturnsExactString() + { + var customId = "test-123_abc@xyz"; + var result = ID.Custom(customId); + Assert.Equal(customId, result); + } + } +} diff --git a/templates/dotnet/Package.Tests/Models/InputFileTests.cs.twig b/templates/dotnet/Package.Tests/Models/InputFileTests.cs.twig new file mode 100644 index 0000000000..61f398d6d5 --- /dev/null +++ b/templates/dotnet/Package.Tests/Models/InputFileTests.cs.twig @@ -0,0 +1,219 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using Xunit; +using {{ spec.title | caseUcfirst }}.Models; + +namespace {{ spec.title | caseUcfirst }}.Tests.Models +{ + public class InputFileTests + { + [Fact] + public void FromPath_WithValidPath_CreatesInputFile() + { + // Arrange + var path = "test.txt"; + + // Act + var inputFile = InputFile.FromPath(path); + + // Assert + Assert.NotNull(inputFile); + Assert.Equal(path, inputFile.Path); + Assert.Equal("test.txt", inputFile.Filename); + Assert.Equal("path", inputFile.SourceType); + Assert.NotNull(inputFile.MimeType); + } + + [Fact] + public void FromPath_ExtractsCorrectFilename() + { + // Arrange + var path = "/some/directory/file.jpg"; + + // Act + var inputFile = InputFile.FromPath(path); + + // Assert + Assert.Equal("file.jpg", inputFile.Filename); + } + + [Fact] + public void FromPath_WithWindowsPath_ExtractsCorrectFilename() + { + // Arrange + var path = @"C:\Users\test\document.pdf"; + + // Act + var inputFile = InputFile.FromPath(path); + + // Assert + string expectedFilename = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "document.pdf" : path; + Assert.Equal(expectedFilename, inputFile.Filename); + } + + [Fact] + public void FromFileInfo_WithValidFileInfo_CreatesInputFile() + { + // Arrange + var tempFile = Path.GetTempFileName(); + var fileInfo = new FileInfo(tempFile); + + try + { + // Act + var inputFile = InputFile.FromFileInfo(fileInfo); + + // Assert + Assert.NotNull(inputFile); + Assert.Equal(fileInfo.FullName, inputFile.Path); + Assert.Equal(fileInfo.Name, inputFile.Filename); + Assert.Equal("path", inputFile.SourceType); + } + finally + { + System.IO.File.Delete(tempFile); + } + } + + [Fact] + public void FromStream_WithValidStream_CreatesInputFile() + { + // Arrange + var stream = new MemoryStream(new byte[] { 1, 2, 3 }); + var filename = "test.bin"; + var mimeType = "application/octet-stream"; + + // Act + var inputFile = InputFile.FromStream(stream, filename, mimeType); + + // Assert + Assert.NotNull(inputFile); + Assert.Equal(stream, inputFile.Data); + Assert.Equal(filename, inputFile.Filename); + Assert.Equal(mimeType, inputFile.MimeType); + Assert.Equal("stream", inputFile.SourceType); + } + + [Fact] + public void FromStream_WithCustomMimeType_SetsCorrectMimeType() + { + // Arrange + var stream = new MemoryStream(); + var customMimeType = "image/png"; + + // Act + var inputFile = InputFile.FromStream(stream, "image.png", customMimeType); + + // Assert + Assert.Equal(customMimeType, inputFile.MimeType); + } + + [Fact] + public void FromBytes_WithValidBytes_CreatesInputFile() + { + // Arrange + var bytes = new byte[] { 1, 2, 3, 4, 5 }; + var filename = "data.bin"; + var mimeType = "application/octet-stream"; + + // Act + var inputFile = InputFile.FromBytes(bytes, filename, mimeType); + + // Assert + Assert.NotNull(inputFile); + Assert.Equal(bytes, inputFile.Data); + Assert.Equal(filename, inputFile.Filename); + Assert.Equal(mimeType, inputFile.MimeType); + Assert.Equal("bytes", inputFile.SourceType); + } + + [Fact] + public void FromBytes_WithEmptyBytes_CreatesInputFile() + { + // Arrange + var bytes = new byte[] { }; + var filename = "empty.bin"; + var mimeType = "application/octet-stream"; + + // Act + var inputFile = InputFile.FromBytes(bytes, filename, mimeType); + + // Assert + Assert.NotNull(inputFile); + Assert.Equal(bytes, inputFile.Data); + Assert.Equal(filename, inputFile.Filename); + } + + [Fact] + public void FromBytes_WithImageData_SetsCorrectMimeType() + { + // Arrange + var bytes = new byte[] { 137, 80, 78, 71 }; // PNG header + var mimeType = "image/png"; + + // Act + var inputFile = InputFile.FromBytes(bytes, "image.png", mimeType); + + // Assert + Assert.Equal(mimeType, inputFile.MimeType); + } + + [Fact] + public void SourceType_Path_IsCorrect() + { + var inputFile = InputFile.FromPath("test.txt"); + Assert.Equal("path", inputFile.SourceType); + } + + [Fact] + public void SourceType_Stream_IsCorrect() + { + var inputFile = InputFile.FromStream(new MemoryStream(), "test.txt", "text/plain"); + Assert.Equal("stream", inputFile.SourceType); + } + + [Fact] + public void SourceType_Bytes_IsCorrect() + { + var inputFile = InputFile.FromBytes(new byte[] { 1 }, "test.bin", "application/octet-stream"); + Assert.Equal("bytes", inputFile.SourceType); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange & Act + var inputFile = new InputFile + { + Path = "custom/path.txt", + Filename = "custom.txt", + MimeType = "text/plain", + SourceType = "custom", + Data = new object() + }; + + // Assert + Assert.Equal("custom/path.txt", inputFile.Path); + Assert.Equal("custom.txt", inputFile.Filename); + Assert.Equal("text/plain", inputFile.MimeType); + Assert.Equal("custom", inputFile.SourceType); + Assert.NotNull(inputFile.Data); + } + + [Fact] + public void DefaultConstructor_InitializesProperties() + { + // Act + var inputFile = new InputFile(); + + // Assert + Assert.NotNull(inputFile); + Assert.NotNull(inputFile.Path); + Assert.NotNull(inputFile.Filename); + Assert.NotNull(inputFile.MimeType); + Assert.NotNull(inputFile.SourceType); + Assert.NotNull(inputFile.Data); + } + } +} diff --git a/templates/dotnet/Package.Tests/Models/ModelTests.cs.twig b/templates/dotnet/Package.Tests/Models/ModelTests.cs.twig new file mode 100644 index 0000000000..fd2d8ac308 --- /dev/null +++ b/templates/dotnet/Package.Tests/Models/ModelTests.cs.twig @@ -0,0 +1,309 @@ +{% set DefinitionClass = definition.name | caseUcfirst | overrideIdentifier %} +{% macro generate_sub_dict(sub_def) %} +new Dictionary { +{% for subprop in sub_def.properties | filter(p => p.required) %} +{ "{{ subprop.name }}", {% if subprop.enum %}{{ subprop.enumName | caseUcfirst }}.{{ (subprop.enumKeys[0] ?? subprop.enum[0]) | caseEnumKey }}.Value{% elseif subprop.type == 'string' %}"{{ subprop['x-example'] | default('test') | escapeCsString }}"{% elseif subprop.type == 'integer' %}{{ subprop['x-example'] | default(1) }}{% elseif subprop.type == 'number' %}{{ subprop['x-example'] | default(1.0) }}{% elseif subprop.type == 'boolean' %}{% if subprop['x-example'] is defined %}{% if subprop['x-example'] is same as(true) or subprop['x-example'] == 'true' or subprop['x-example'] == 1 %}true{% else %}false{% endif %}{% else %}true{% endif %}{% elseif subprop.sub_schema %}new Dictionary(){% else %}"{{ subprop['x-example'] | default('test') | escapeCsString }}"{% endif %} }{% if not loop.last %},{% endif %} +{% endfor %} +} +{% endmacro %} +using System; +using System.Collections.Generic; +using Xunit; +using {{ spec.title | caseUcfirst }}.Models; +{% if definition.properties | filter(p => p.enum) | length > 0 %} +using {{ spec.title | caseUcfirst }}.Enums; +{% endif %} + +namespace {{ spec.title | caseUcfirst }}.Tests.Models +{ + public class {{ DefinitionClass }}Tests + { + [Fact] + public void Constructor_WithValidParameters_CreatesInstance() + { + // Arrange & Act + var model = new {{ spec.title | caseUcfirst }}.Models.{{ DefinitionClass }}( + {%~ for property in definition.properties %} + {%~ if property.enum %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property.enumName | caseUcfirst }}.{{ (property.enumKeys[0] ?? property.enum[0]) | caseEnumKey }} + {%~ elseif property.type == 'string' %} + {{ property.name | caseCamel | escapeKeyword }}: "{{ property['x-example'] | default('test') | escapeCsString }}" + {%~ elseif property.type == 'boolean' %} + {%~ if property['x-example'] is defined %} + {{ property.name | caseCamel | escapeKeyword }}: {% if property['x-example'] is same as(true) or property['x-example'] == 'true' or property['x-example'] == 1 %}true{% else %}false{% endif %} + {%~ else %} + {{ property.name | caseCamel | escapeKeyword }}: true + {%~ endif %} + {%~ elseif property.type == 'integer' %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property['x-example'] | default(1) }} + {%~ elseif property.type == 'number' %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property['x-example'] | default(1.0) }} + {%~ elseif property.type == 'array' %} + {%~ set itemType = test_item_type(property) %} + {{ property.name | caseCamel | escapeKeyword }}: new List<{{ itemType }}>() + {%~ elseif property.type == 'object' and not property.sub_schema %} + {{ property.name | caseCamel | escapeKeyword }}: new Dictionary() + {%~ elseif property.sub_schema %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property.sub_schema | caseUcfirst }}.From({{ _self.generate_sub_dict(spec.definitions | filter(d => d.name == property.sub_schema) | first) }}) + {%~ else %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property['x-example'] | default('null') | escapeCsString }} + {%~ endif -%} + {%~ if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} + {%~ endfor %} + {%~ if definition.additionalProperties %} + data: new Dictionary() + {%~ endif %} + ); + + // Assert + Assert.NotNull(model); + {%~ for property in definition.properties %} + {%~ if property.enum %} + Assert.Equal({{ property.enumName | caseUcfirst }}.{{ (property.enumKeys[0] ?? property.enum[0]) | caseEnumKey }}.Value, model.{{ property_name(definition, property) | overrideProperty(definition.name) }}.Value); + {%~ elseif property.type == 'string' %} + Assert.Equal("{{ property['x-example'] | default('test') | escapeCsString }}", model.{{ property_name(definition, property) | overrideProperty(definition.name) }}); + {%~ elseif property.type == 'boolean' %} + {%~ if property['x-example'] is defined %} + Assert.Equal({% if property['x-example'] is same as(true) or property['x-example'] == 'true' or property['x-example'] == 1 %}true{% else %}false{% endif %}, model.{{ property_name(definition, property) | overrideProperty(definition.name) }}); + {%~ else %} + Assert.Equal(true, model.{{ property_name(definition, property) | overrideProperty(definition.name) }}); + {%~ endif %} + {%~ 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 == 'array' or (property.type == 'object' and not property.sub_schema) %} + Assert.NotNull(model.{{ property_name(definition, property) | overrideProperty(definition.name) }}); + {%~ endif %} + {%~ endfor %} + } + + [Fact] + public void ToMap_ReturnsCorrectDictionary() + { + // Arrange + var model = new {{ spec.title | caseUcfirst }}.Models.{{ DefinitionClass }}( + {%~ for property in definition.properties %} + {%~ if property.enum %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property.enumName | caseUcfirst }}.{{ (property.enumKeys[0] ?? property.enum[0]) | caseEnumKey }} + {%~ elseif property.type == 'string' %} + {{ property.name | caseCamel | escapeKeyword }}: "{{ property['x-example'] | default('test') | escapeCsString }}" + {%~ elseif property.type == 'integer' %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property['x-example'] | default(1) }} + {%~ elseif property.type == 'number' %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property['x-example'] | default(1.0) }} + {%~ elseif property.type == 'boolean' %} + {%~ if property['x-example'] is defined %} + {{ property.name | caseCamel | escapeKeyword }}: {% if property['x-example'] is same as(true) or property['x-example'] == 'true' or property['x-example'] == 1 %}true{% else %}false{% endif %} + {%~ else %} + {{ property.name | caseCamel | escapeKeyword }}: true + {%~ endif %} + {%~ elseif property.type == 'array' %} + {%~ set itemType = test_item_type(property) %} + {{ property.name | caseCamel | escapeKeyword }}: new List<{{ itemType }}>() + {%~ elseif property.type == 'object' and not property.sub_schema %} + {{ property.name | caseCamel | escapeKeyword }}: new Dictionary() + {%~ elseif property.sub_schema %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property.sub_schema | caseUcfirst }}.From({{ _self.generate_sub_dict(spec.definitions | filter(d => d.name == property.sub_schema) | first) }}) + {%~ else %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property['x-example'] | default('null') }} + {%~ endif %} + {%~ if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} + {%~ endfor %} + {%~ if definition.additionalProperties %} + data: new Dictionary() + {%~ endif %} + ); + + // Act + var map = model.ToMap(); + + // Assert + Assert.NotNull(map); + {%~ for property in definition.properties %} + Assert.True(map.ContainsKey("{{ property.name }}")); + {%~ endfor %} + } + + [Fact] + public void From_WithValidMap_CreatesInstance() + { + // Arrange + var map = new Dictionary + { + {%~ for property in definition.properties | filter(p => p.required) %} + {%~ if property.enum %} + { "{{ property.name }}", {{ property.enumName | caseUcfirst }}.{{ (property.enumKeys[0] ?? property.enum[0]) | caseEnumKey }}.Value } + {%~ elseif property.type == 'string' %} + { "{{ property.name }}", "{{ property['x-example'] | default('test') | escapeCsString }}" } + {%~ elseif property.type == 'integer' %} + { "{{ property.name }}", {{ property['x-example'] | default(1) }} } + {%~ elseif property.type == 'number' %} + { "{{ property.name }}", {{ property['x-example'] | default(1.0) }} } + {%~ elseif property.type == 'boolean' %} + {%~ if property['x-example'] is defined %} + { "{{ property.name }}", {% if property['x-example'] is same as(true) or property['x-example'] == 'true' or property['x-example'] == 1 %}true{% else %}false{% endif %} } + {%~ else %} + { "{{ property.name }}", true } + {%~ endif %} + {%~ elseif property.type == 'array' %} + { "{{ property.name }}", new List() } + {%~ elseif property.type == 'object' and not property.sub_schema %} + { "{{ property.name }}", new Dictionary() } + {%~ elseif property.sub_schema %} + { "{{ property.name }}", {{ _self.generate_sub_dict(spec.definitions | filter(d => d.name == property.sub_schema) | first) }} } + {%~ else %} + { "{{ property.name }}", "{{ property['x-example'] | default('test') | escapeCsString }}" } + {%~ endif %} + {%~ if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} + {%~ endfor %} + }; + + // Act + var model = {{ spec.title | caseUcfirst }}.Models.{{ DefinitionClass }}.From(map); + + // Assert + Assert.NotNull(model); + {%~ for property in definition.properties | filter(p => p.required) %} + Assert.NotNull(model.{{ property_name(definition, property) | overrideProperty(definition.name) }}); + {%~ endfor %} + } + + [Fact] + public void ToMap_AndFrom_RoundTrip_PreservesData() + { + // Arrange + var original = new {{ spec.title | caseUcfirst }}.Models.{{ DefinitionClass }}( + {%~ for property in definition.properties %} + {%~ if property.enum %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property.enumName | caseUcfirst }}.{{ (property.enumKeys[0] ?? property.enum[0]) | caseEnumKey }} + {%~ elseif property.type == 'string' %} + {{ property.name | caseCamel | escapeKeyword }}: "{{ property['x-example'] | default('test') | escapeCsString }}" + {%~ elseif property.type == 'integer' %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property['x-example'] | default(1) }} + {%~ elseif property.type == 'number' %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property['x-example'] | default(1.0) }} + {%~ elseif property.type == 'boolean' %} + {%~ if property['x-example'] is defined %} + {{ property.name | caseCamel | escapeKeyword }}: {% if property['x-example'] is same as(true) or property['x-example'] == 'true' or property['x-example'] == 1 %}true{% else %}false{% endif %} + {%~ else %} + {{ property.name | caseCamel | escapeKeyword }}: true + {%~ endif %} + {%~ elseif property.type == 'array' %} + {%~ set itemType = test_item_type(property) %} + {{ property.name | caseCamel | escapeKeyword }}: new List<{{ itemType }}>() + {%~ elseif property.type == 'object' and not property.sub_schema %} + {{ property.name | caseCamel | escapeKeyword }}: new Dictionary() + {%~ elseif property.sub_schema %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property.sub_schema | caseUcfirst }}.From({{ _self.generate_sub_dict(spec.definitions | filter(d => d.name == property.sub_schema) | first) }}) + {%~ else %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property['x-example'] | default('null') }} + {%~ endif %} + {%~ if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} + {%~ endfor %} + {%~ if definition.additionalProperties %} + data: new Dictionary() + {%~ endif %} + ); + + // Act + var map = original.ToMap(); + var result = {{ spec.title | caseUcfirst }}.Models.{{ DefinitionClass }}.From(map); + + // Assert + {%~ for property in definition.properties | filter(p => p.required) %} + {%~ if property.enum %} + Assert.Equal(original.{{ property_name(definition, property) | overrideProperty(definition.name) }}.Value, result.{{ property_name(definition, property) | overrideProperty(definition.name) }}.Value); + {%~ elseif property.type == 'string' or property.type == 'integer' or property.type == 'number' or property.type == 'boolean' %} + Assert.Equal(original.{{ property_name(definition, property) | overrideProperty(definition.name) }}, result.{{ property_name(definition, property) | overrideProperty(definition.name) }}); + {%~ endif %} + {%~ endfor %} + } + {%~ if definition.additionalProperties %} + + [Fact] + public void ConvertTo_WithValidFunction_ConvertsCorrectly() + { + // Arrange + var data = new Dictionary + { + { "customKey", "customValue" } + }; + var model = new {{ spec.title | caseUcfirst }}.Models.{{ DefinitionClass }}( + {%~ for property in definition.properties %} + {%~ if property.type == 'string' %} + {{ property.name | caseCamel | escapeKeyword }}: "test" + {%~ elseif property.type == 'integer' %} + {{ property.name | caseCamel | escapeKeyword }}: 1 + {%~ elseif property.type == 'number' %} + {{ property.name | caseCamel | escapeKeyword }}: 1.0 + {%~ elseif property.type == 'boolean' %} + {{ property.name | caseCamel | escapeKeyword }}: true + {%~ elseif property.type == 'array' %} + {%~ set itemType = test_item_type(property) %} + {{ property.name | caseCamel | escapeKeyword }}: new List<{{ itemType }}>() + {%~ elseif property.type == 'object' and not property.sub_schema %} + {{ property.name | caseCamel | escapeKeyword }}: new Dictionary() + {%~ elseif property.enum %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property.enumName | caseUcfirst }}.{{ (property.enumKeys[0] ?? property.enum[0]) | caseEnumKey }} + {%~ elseif property.sub_schema %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property.sub_schema | caseUcfirst }}.From({{ _self.generate_sub_dict(spec.definitions | filter(d => d.name == property.sub_schema) | first) }}) + {%~ else %} + {{ property.name | caseCamel | escapeKeyword }}: null + {%~ endif %} + {%~ if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} + {%~ endfor %} + {%~ if definition.additionalProperties %} + data: data + {%~ endif %} + ); + + // Act + var result = model.ConvertTo(d => d["customKey"].ToString()); + + // Assert + Assert.Equal("customValue", result); + } + {%~ endif %} + + [Fact] + public void Properties_AreReadOnly() + { + // Arrange + var model = new {{ spec.title | caseUcfirst }}.Models.{{ DefinitionClass }}( + {%~ for property in definition.properties %} + {%~ if property.enum %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property.enumName | caseUcfirst }}.{{ (property.enumKeys[0] ?? property.enum[0]) | caseEnumKey }} + {%~ elseif property.type == 'string' %} + {{ property.name | caseCamel | escapeKeyword }}: "test" + {%~ elseif property.type == 'integer' %} + {{ property.name | caseCamel | escapeKeyword }}: 1 + {%~ elseif property.type == 'number' %} + {{ property.name | caseCamel | escapeKeyword }}: 1.0 + {%~ elseif property.type == 'boolean' %} + {{ property.name | caseCamel | escapeKeyword }}: true + {%~ elseif property.type == 'array' %} + {%~ set itemType = test_item_type(property) %} + {{ property.name | caseCamel | escapeKeyword }}: new List<{{ itemType }}>() + {%~ elseif property.type == 'object' and not property.sub_schema %} + {{ property.name | caseCamel | escapeKeyword }}: new Dictionary() + {%~ elseif property.sub_schema %} + {{ property.name | caseCamel | escapeKeyword }}: {{ property.sub_schema | caseUcfirst }}.From({{ _self.generate_sub_dict(spec.definitions | filter(d => d.name == property.sub_schema) | first) }}) + {%~ else %} + {{ property.name | caseCamel | escapeKeyword }}: null + {%~ endif %} + {%~ if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} + {%~ endfor %} + {%~ if definition.additionalProperties %} + data: new Dictionary() + {%~ endif %} + ); + + // Assert - properties should have private setters + {%~ for property in definition.properties | slice(0, 1) %} + var propertyInfo = typeof({{ spec.title | caseUcfirst }}.Models.{{ DefinitionClass }}).GetProperty("{{ property_name(definition, property) | overrideProperty(definition.name) }}"); + Assert.NotNull(propertyInfo); + Assert.Null(propertyInfo.GetSetMethod()); + {%~ endfor %} + } + } +} + diff --git a/templates/dotnet/Package.Tests/OperatorTests.cs.twig b/templates/dotnet/Package.Tests/OperatorTests.cs.twig new file mode 100644 index 0000000000..8cba20d45e --- /dev/null +++ b/templates/dotnet/Package.Tests/OperatorTests.cs.twig @@ -0,0 +1,335 @@ +using System.Collections.Generic; +using System.Text.Json; +using Xunit; + +namespace {{ spec.title | caseUcfirst }}.Tests +{ + public class OperatorTests + { + [Fact] + public void Increment_ReturnsCorrectOperator() + { + var result = Operator.Increment(1); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("increment", op.Method); + Assert.NotNull(op.Values); + Assert.Single(op.Values); + Assert.Equal(1, ((JsonElement)op.Values[0]).GetInt32()); + } + + [Fact] + public void Increment_WithMax_ReturnsCorrectOperator() + { + var result = Operator.Increment(5, 100); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("increment", op.Method); + Assert.NotNull(op.Values); + Assert.Equal(2, op.Values.Count); + Assert.Equal(5, ((JsonElement)op.Values[0]).GetInt32()); + Assert.Equal(100, ((JsonElement)op.Values[1]).GetInt32()); + } + + [Fact] + public void Decrement_ReturnsCorrectOperator() + { + var result = Operator.Decrement(1); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("decrement", op.Method); + Assert.NotNull(op.Values); + Assert.Single(op.Values); + Assert.Equal(1, ((JsonElement)op.Values[0]).GetInt32()); + } + + [Fact] + public void Decrement_WithMin_ReturnsCorrectOperator() + { + var result = Operator.Decrement(3, 0); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("decrement", op.Method); + Assert.NotNull(op.Values); + Assert.Equal(2, op.Values.Count); + Assert.Equal(3, ((JsonElement)op.Values[0]).GetInt32()); + Assert.Equal(0, ((JsonElement)op.Values[1]).GetInt32()); + } + + [Fact] + public void Multiply_ReturnsCorrectOperator() + { + var result = Operator.Multiply(2); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("multiply", op.Method); + Assert.NotNull(op.Values); + Assert.Single(op.Values); + Assert.Equal(2, ((JsonElement)op.Values[0]).GetInt32()); + } + + [Fact] + public void Multiply_WithMax_ReturnsCorrectOperator() + { + var result = Operator.Multiply(3, 1000); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("multiply", op.Method); + Assert.NotNull(op.Values); + Assert.Equal(2, op.Values.Count); + Assert.Equal(3, ((JsonElement)op.Values[0]).GetInt32()); + Assert.Equal(1000, ((JsonElement)op.Values[1]).GetInt32()); + } + + [Fact] + public void Divide_ReturnsCorrectOperator() + { + var result = Operator.Divide(2); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("divide", op.Method); + Assert.NotNull(op.Values); + Assert.Single(op.Values); + Assert.Equal(2, ((JsonElement)op.Values[0]).GetInt32()); + } + + [Fact] + public void Divide_WithMin_ReturnsCorrectOperator() + { + var result = Operator.Divide(4, 1); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("divide", op.Method); + Assert.NotNull(op.Values); + Assert.Equal(2, op.Values.Count); + Assert.Equal(4, ((JsonElement)op.Values[0]).GetInt32()); + Assert.Equal(1, ((JsonElement)op.Values[1]).GetInt32()); + } + + [Fact] + public void Modulo_ReturnsCorrectOperator() + { + var result = Operator.Modulo(5); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("modulo", op.Method); + Assert.NotNull(op.Values); + Assert.Single(op.Values); + Assert.Equal(5, ((JsonElement)op.Values[0]).GetInt32()); + } + + [Fact] + public void Power_ReturnsCorrectOperator() + { + var result = Operator.Power(2); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("power", op.Method); + Assert.NotNull(op.Values); + Assert.Single(op.Values); + Assert.Equal(2, ((JsonElement)op.Values[0]).GetInt32()); + } + + [Fact] + public void Power_WithMax_ReturnsCorrectOperator() + { + var result = Operator.Power(3, 100); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("power", op.Method); + Assert.NotNull(op.Values); + Assert.Equal(2, op.Values.Count); + Assert.Equal(3, ((JsonElement)op.Values[0]).GetInt32()); + Assert.Equal(100, ((JsonElement)op.Values[1]).GetInt32()); + } + + [Fact] + public void ArrayAppend_ReturnsCorrectOperator() + { + var result = Operator.ArrayAppend(new List() { "item1", "item2" }); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("arrayAppend", op.Method); + Assert.NotNull(op.Values); + Assert.Equal(2, op.Values.Count); + } + + [Fact] + public void ArrayPrepend_ReturnsCorrectOperator() + { + var result = Operator.ArrayPrepend(new List() { "first", "second" }); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("arrayPrepend", op.Method); + Assert.NotNull(op.Values); + Assert.Equal(2, op.Values.Count); + } + + [Fact] + public void ArrayInsert_ReturnsCorrectOperator() + { + var result = Operator.ArrayInsert(0, "newItem"); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("arrayInsert", op.Method); + Assert.NotNull(op.Values); + Assert.Equal(2, op.Values.Count); + Assert.Equal(0, ((JsonElement)op.Values[0]).GetInt32()); + Assert.Equal("newItem", op.Values[1].ToString()); + } + + [Fact] + public void ArrayRemove_ReturnsCorrectOperator() + { + var result = Operator.ArrayRemove("oldItem"); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("arrayRemove", op.Method); + Assert.NotNull(op.Values); + Assert.Single(op.Values); + Assert.Equal("oldItem", op.Values[0].ToString()); + } + + [Fact] + public void ArrayUnique_ReturnsCorrectOperator() + { + var result = Operator.ArrayUnique(); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("arrayUnique", op.Method); + Assert.NotNull(op.Values); + Assert.Empty(op.Values); + } + + [Fact] + public void ArrayIntersect_ReturnsCorrectOperator() + { + var result = Operator.ArrayIntersect(new List() { "a", "b", "c" }); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("arrayIntersect", op.Method); + Assert.NotNull(op.Values); + Assert.Equal(3, op.Values.Count); + } + + [Fact] + public void ArrayDiff_ReturnsCorrectOperator() + { + var result = Operator.ArrayDiff(new List() { "x", "y" }); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("arrayDiff", op.Method); + Assert.NotNull(op.Values); + Assert.Equal(2, op.Values.Count); + } + + [Fact] + public void ArrayFilter_ReturnsCorrectOperator() + { + var result = Operator.ArrayFilter(Condition.Equal, "test"); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("arrayFilter", op.Method); + Assert.NotNull(op.Values); + Assert.Equal(2, op.Values.Count); + Assert.Equal("equal", op.Values[0].ToString()); + Assert.Equal("test", op.Values[1].ToString()); + } + + [Fact] + public void StringConcat_ReturnsCorrectOperator() + { + var result = Operator.StringConcat("suffix"); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("stringConcat", op.Method); + Assert.NotNull(op.Values); + Assert.Single(op.Values); + Assert.Equal("suffix", op.Values[0].ToString()); + } + + [Fact] + public void StringReplace_ReturnsCorrectOperator() + { + var result = Operator.StringReplace("old", "new"); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("stringReplace", op.Method); + Assert.NotNull(op.Values); + Assert.Equal(2, op.Values.Count); + Assert.Equal("old", op.Values[0].ToString()); + Assert.Equal("new", op.Values[1].ToString()); + } + + [Fact] + public void Toggle_ReturnsCorrectOperator() + { + var result = Operator.Toggle(); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("toggle", op.Method); + Assert.NotNull(op.Values); + Assert.Empty(op.Values); + } + + [Fact] + public void DateAddDays_ReturnsCorrectOperator() + { + var result = Operator.DateAddDays(7); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("dateAddDays", op.Method); + Assert.NotNull(op.Values); + Assert.Single(op.Values); + Assert.Equal(7, ((JsonElement)op.Values[0]).GetInt32()); + } + + [Fact] + public void DateSubDays_ReturnsCorrectOperator() + { + var result = Operator.DateSubDays(3); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("dateSubDays", op.Method); + Assert.NotNull(op.Values); + Assert.Single(op.Values); + Assert.Equal(3, ((JsonElement)op.Values[0]).GetInt32()); + } + + [Fact] + public void DateSetNow_ReturnsCorrectOperator() + { + var result = Operator.DateSetNow(); + var op = JsonSerializer.Deserialize(result); + + Assert.NotNull(op); + Assert.Equal("dateSetNow", op.Method); + Assert.NotNull(op.Values); + Assert.Empty(op.Values); + } + } +} diff --git a/templates/dotnet/Package.Tests/PermissionTests.cs.twig b/templates/dotnet/Package.Tests/PermissionTests.cs.twig new file mode 100644 index 0000000000..b58ffea8fa --- /dev/null +++ b/templates/dotnet/Package.Tests/PermissionTests.cs.twig @@ -0,0 +1,78 @@ +using Xunit; +using {{ spec.title | caseUcfirst }}; + +namespace {{ spec.title | caseUcfirst }}.Tests +{ + public class PermissionTests + { + [Fact] + public void Read_ReturnsCorrectPermission() + { + var result = Permission.Read(Role.Any()); + Assert.Equal("read(\"any\")", result); + } + + [Fact] + public void Write_ReturnsCorrectPermission() + { + var result = Permission.Write(Role.Any()); + Assert.Equal("write(\"any\")", result); + } + + [Fact] + public void Create_ReturnsCorrectPermission() + { + var result = Permission.Create(Role.Any()); + Assert.Equal("create(\"any\")", result); + } + + [Fact] + public void Update_ReturnsCorrectPermission() + { + var result = Permission.Update(Role.Any()); + Assert.Equal("update(\"any\")", result); + } + + [Fact] + public void Delete_ReturnsCorrectPermission() + { + var result = Permission.Delete(Role.Any()); + Assert.Equal("delete(\"any\")", result); + } + + [Fact] + public void Read_WithUserRole_ReturnsCorrectFormat() + { + var result = Permission.Read(Role.User("123")); + Assert.Equal("read(\"user:123\")", result); + } + + [Fact] + public void Write_WithTeamRole_ReturnsCorrectFormat() + { + var result = Permission.Write(Role.Team("team123", "owner")); + Assert.Equal("write(\"team:team123/owner\")", result); + } + + [Fact] + public void Create_WithGuestsRole_ReturnsCorrectFormat() + { + var result = Permission.Create(Role.Guests()); + Assert.Equal("create(\"guests\")", result); + } + + [Fact] + public void Update_WithLabelRole_ReturnsCorrectFormat() + { + var result = Permission.Update(Role.Label("admin")); + Assert.Equal("update(\"label:admin\")", result); + } + + [Fact] + public void Delete_WithMemberRole_ReturnsCorrectFormat() + { + var result = Permission.Delete(Role.Member("member123")); + Assert.Equal("delete(\"member:member123\")", result); + } + } +} diff --git a/templates/dotnet/Package.Tests/QueryTests.cs.twig b/templates/dotnet/Package.Tests/QueryTests.cs.twig new file mode 100644 index 0000000000..64a6c0bc9c --- /dev/null +++ b/templates/dotnet/Package.Tests/QueryTests.cs.twig @@ -0,0 +1,580 @@ +using System.Collections.Generic; +using System.Text.Json; +using Xunit; + +namespace {{ spec.title | caseUcfirst }}.Tests +{ + public class QueryTests + { + [Fact] + public void Equal_WithString_ReturnsCorrectQuery() + { + var result = Query.Equal("attr", "value"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("equal", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("value", query.Values[0].ToString()); + } + + [Fact] + public void Equal_WithInteger_ReturnsCorrectQuery() + { + var result = Query.Equal("attr", 1); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("equal", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal(1, ((JsonElement)query.Values[0]).GetInt32()); + } + + [Fact] + public void Equal_WithDouble_ReturnsCorrectQuery() + { + var result = Query.Equal("attr", 1.5); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("equal", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal(1.5, ((JsonElement)query.Values[0]).GetDouble()); + } + + [Fact] + public void Equal_WithBoolean_ReturnsCorrectQuery() + { + var result = Query.Equal("attr", true); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("equal", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.True(((JsonElement)query.Values[0]).GetBoolean()); + } + + [Fact] + public void Equal_WithList_ReturnsCorrectQuery() + { + var result = Query.Equal("attr", new[] { "a", "b", "c" }); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("equal", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Equal(3, query.Values.Count); + } + + [Fact] + public void NotEqual_WithString_ReturnsCorrectQuery() + { + var result = Query.NotEqual("attr", "value"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("notEqual", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + } + + [Fact] + public void LessThan_WithInteger_ReturnsCorrectQuery() + { + var result = Query.LessThan("attr", 10); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("lessThan", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + } + + [Fact] + public void LessThanEqual_WithInteger_ReturnsCorrectQuery() + { + var result = Query.LessThanEqual("attr", 10); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("lessThanEqual", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + } + + [Fact] + public void GreaterThan_WithInteger_ReturnsCorrectQuery() + { + var result = Query.GreaterThan("attr", 5); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("greaterThan", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + } + + [Fact] + public void GreaterThanEqual_WithInteger_ReturnsCorrectQuery() + { + var result = Query.GreaterThanEqual("attr", 5); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("greaterThanEqual", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + } + + [Fact] + public void Search_ReturnsCorrectQuery() + { + var result = Query.Search("attr", "keyword1 keyword2"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("search", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("keyword1 keyword2", query.Values[0].ToString()); + } + + [Fact] + public void IsNull_ReturnsCorrectQuery() + { + var result = Query.IsNull("attr"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("isNull", query.Method); + Assert.Equal("attr", query.Attribute); + } + + [Fact] + public void IsNotNull_ReturnsCorrectQuery() + { + var result = Query.IsNotNull("attr"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("isNotNull", query.Method); + Assert.Equal("attr", query.Attribute); + } + + [Fact] + public void Between_WithIntegers_ReturnsCorrectQuery() + { + var result = Query.Between("attr", 1, 10); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("between", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Equal(2, query.Values.Count); + } + + [Fact] + public void Between_WithDoubles_ReturnsCorrectQuery() + { + var result = Query.Between("attr", 1.5, 10.5); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("between", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Equal(2, query.Values.Count); + } + + [Fact] + public void Between_WithStrings_ReturnsCorrectQuery() + { + var result = Query.Between("attr", "a", "z"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("between", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Equal(2, query.Values.Count); + } + + [Fact] + public void StartsWith_ReturnsCorrectQuery() + { + var result = Query.StartsWith("attr", "prefix"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("startsWith", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("prefix", query.Values[0].ToString()); + } + + [Fact] + public void EndsWith_ReturnsCorrectQuery() + { + var result = Query.EndsWith("attr", "suffix"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("endsWith", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("suffix", query.Values[0].ToString()); + } + + [Fact] + public void Select_WithSingleAttribute_ReturnsCorrectQuery() + { + var result = Query.Select(new List() { "attr1" }); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("select", query.Method); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + } + + [Fact] + public void Select_WithMultipleAttributes_ReturnsCorrectQuery() + { + var result = Query.Select(new List() { "attr1", "attr2", "attr3" }); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("select", query.Method); + Assert.NotNull(query.Values); + Assert.Equal(3, query.Values.Count); + } + + [Fact] + public void OrderAsc_ReturnsCorrectQuery() + { + var result = Query.OrderAsc("attr"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("orderAsc", query.Method); + Assert.Equal("attr", query.Attribute); + } + + [Fact] + public void OrderDesc_ReturnsCorrectQuery() + { + var result = Query.OrderDesc("attr"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("orderDesc", query.Method); + Assert.Equal("attr", query.Attribute); + } + + [Fact] + public void CursorAfter_ReturnsCorrectQuery() + { + var result = Query.CursorAfter("documentId"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("cursorAfter", query.Method); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("documentId", query.Values[0].ToString()); + } + + [Fact] + public void CursorBefore_ReturnsCorrectQuery() + { + var result = Query.CursorBefore("documentId"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("cursorBefore", query.Method); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("documentId", query.Values[0].ToString()); + } + + [Fact] + public void Limit_ReturnsCorrectQuery() + { + var result = Query.Limit(25); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("limit", query.Method); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal(25, ((JsonElement)query.Values[0]).GetInt32()); + } + + [Fact] + public void Offset_ReturnsCorrectQuery() + { + var result = Query.Offset(10); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("offset", query.Method); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal(10, ((JsonElement)query.Values[0]).GetInt32()); + } + + [Fact] + public void Contains_ReturnsCorrectQuery() + { + var result = Query.Contains("attr", "value"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("contains", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("value", query.Values[0].ToString()); + } + + [Fact] + public void Or_WithMultipleQueries_ReturnsCorrectQuery() + { + var result = Query.Or(new List() { + Query.Equal("attr1", "value1"), + Query.Equal("attr2", "value2") + }); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("or", query.Method); + Assert.NotNull(query.Values); + Assert.Equal(2, query.Values.Count); + } + + [Fact] + public void And_WithMultipleQueries_ReturnsCorrectQuery() + { + var result = Query.And(new List() { + Query.Equal("attr1", "value1"), + Query.Equal("attr2", "value2") + }); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("and", query.Method); + Assert.NotNull(query.Values); + Assert.Equal(2, query.Values.Count); + } + + [Fact] + public void NotContains_ReturnsCorrectQuery() + { + var result = Query.NotContains("attr", "value"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("notContains", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("value", query.Values[0].ToString()); + } + + [Fact] + public void NotSearch_ReturnsCorrectQuery() + { + var result = Query.NotSearch("attr", "keyword1 keyword2"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("notSearch", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("keyword1 keyword2", query.Values[0].ToString()); + } + + [Fact] + public void NotBetween_WithIntegers_ReturnsCorrectQuery() + { + var result = Query.NotBetween("attr", 1, 2); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("notBetween", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Equal(2, query.Values.Count); + Assert.Equal(1, ((JsonElement)query.Values[0]).GetInt32()); + Assert.Equal(2, ((JsonElement)query.Values[1]).GetInt32()); + } + + [Fact] + public void NotBetween_WithDoubles_ReturnsCorrectQuery() + { + var result = Query.NotBetween("attr", 1.0, 2.0); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("notBetween", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Equal(2, query.Values.Count); + Assert.Equal(1.0, ((JsonElement)query.Values[0]).GetDouble()); + Assert.Equal(2.0, ((JsonElement)query.Values[1]).GetDouble()); + } + + [Fact] + public void NotBetween_WithStrings_ReturnsCorrectQuery() + { + var result = Query.NotBetween("attr", "a", "z"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("notBetween", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Equal(2, query.Values.Count); + Assert.Equal("a", query.Values[0].ToString()); + Assert.Equal("z", query.Values[1].ToString()); + } + + [Fact] + public void NotStartsWith_ReturnsCorrectQuery() + { + var result = Query.NotStartsWith("attr", "prefix"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("notStartsWith", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("prefix", query.Values[0].ToString()); + } + + [Fact] + public void NotEndsWith_ReturnsCorrectQuery() + { + var result = Query.NotEndsWith("attr", "suffix"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("notEndsWith", query.Method); + Assert.Equal("attr", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("suffix", query.Values[0].ToString()); + } + + [Fact] + public void CreatedBefore_ReturnsCorrectQuery() + { + var result = Query.CreatedBefore("2023-01-01"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("lessThan", query.Method); + Assert.Equal("$createdAt", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("2023-01-01", query.Values[0].ToString()); + } + + [Fact] + public void CreatedAfter_ReturnsCorrectQuery() + { + var result = Query.CreatedAfter("2023-01-01"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("greaterThan", query.Method); + Assert.Equal("$createdAt", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("2023-01-01", query.Values[0].ToString()); + } + + [Fact] + public void CreatedBetween_ReturnsCorrectQuery() + { + var result = Query.CreatedBetween("2023-01-01", "2023-12-31"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("between", query.Method); + Assert.Equal("$createdAt", query.Attribute); + Assert.NotNull(query.Values); + Assert.Equal(2, query.Values.Count); + Assert.Equal("2023-01-01", query.Values[0].ToString()); + Assert.Equal("2023-12-31", query.Values[1].ToString()); + } + + [Fact] + public void UpdatedBefore_ReturnsCorrectQuery() + { + var result = Query.UpdatedBefore("2023-01-01"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("lessThan", query.Method); + Assert.Equal("$updatedAt", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("2023-01-01", query.Values[0].ToString()); + } + + [Fact] + public void UpdatedAfter_ReturnsCorrectQuery() + { + var result = Query.UpdatedAfter("2023-01-01"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("greaterThan", query.Method); + Assert.Equal("$updatedAt", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("2023-01-01", query.Values[0].ToString()); + } + + [Fact] + public void UpdatedBetween_ReturnsCorrectQuery() + { + var result = Query.UpdatedBetween("2023-01-01", "2023-12-31"); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("between", query.Method); + Assert.Equal("$updatedAt", query.Attribute); + Assert.NotNull(query.Values); + Assert.Equal(2, query.Values.Count); + Assert.Equal("2023-01-01", query.Values[0].ToString()); + Assert.Equal("2023-12-31", query.Values[1].ToString()); + } + + [Fact] + public void OrderRandom_ReturnsCorrectQuery() + { + var result = Query.OrderRandom(); + var query = JsonSerializer.Deserialize(result); + + Assert.NotNull(query); + Assert.Equal("orderRandom", query.Method); + } + } +} diff --git a/templates/dotnet/Package.Tests/RoleTests.cs.twig b/templates/dotnet/Package.Tests/RoleTests.cs.twig new file mode 100644 index 0000000000..df11633421 --- /dev/null +++ b/templates/dotnet/Package.Tests/RoleTests.cs.twig @@ -0,0 +1,108 @@ +using Xunit; +using {{ spec.title | caseUcfirst }}; + +namespace {{ spec.title | caseUcfirst }}.Tests +{ + public class RoleTests + { + [Fact] + public void Any_ReturnsCorrectRole() + { + var result = Role.Any(); + Assert.Equal("any", result); + } + + [Fact] + public void User_WithoutStatus_ReturnsCorrectRole() + { + var result = Role.User("custom"); + Assert.Equal("user:custom", result); + } + + [Fact] + public void User_WithStatus_ReturnsCorrectRole() + { + var result = Role.User("custom", "verified"); + Assert.Equal("user:custom/verified", result); + } + + [Fact] + public void User_WithUnverifiedStatus_ReturnsCorrectRole() + { + var result = Role.User("user123", "unverified"); + Assert.Equal("user:user123/unverified", result); + } + + [Fact] + public void Users_WithoutStatus_ReturnsCorrectRole() + { + var result = Role.Users(); + Assert.Equal("users", result); + } + + [Fact] + public void Users_WithStatus_ReturnsCorrectRole() + { + var result = Role.Users("verified"); + Assert.Equal("users/verified", result); + } + + [Fact] + public void Users_WithUnverifiedStatus_ReturnsCorrectRole() + { + var result = Role.Users("unverified"); + Assert.Equal("users/unverified", result); + } + + [Fact] + public void Guests_ReturnsCorrectRole() + { + var result = Role.Guests(); + Assert.Equal("guests", result); + } + + [Fact] + public void Team_WithoutRole_ReturnsCorrectRole() + { + var result = Role.Team("custom"); + Assert.Equal("team:custom", result); + } + + [Fact] + public void Team_WithRole_ReturnsCorrectRole() + { + var result = Role.Team("custom", "owner"); + Assert.Equal("team:custom/owner", result); + } + + [Fact] + public void Team_WithMemberRole_ReturnsCorrectRole() + { + var result = Role.Team("team123", "member"); + Assert.Equal("team:team123/member", result); + } + + [Fact] + public void Member_ReturnsCorrectRole() + { + var result = Role.Member("custom"); + Assert.Equal("member:custom", result); + } + + [Fact] + public void Label_ReturnsCorrectRole() + { + var result = Role.Label("admin"); + Assert.Equal("label:admin", result); + } + + [Fact] + public void Label_WithMultipleLabels_ReturnsCorrectRole() + { + var result1 = Role.Label("moderator"); + var result2 = Role.Label("vip"); + Assert.Equal("label:moderator", result1); + Assert.Equal("label:vip", result2); + } + } +} diff --git a/templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig b/templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig new file mode 100644 index 0000000000..cf19242ffd --- /dev/null +++ b/templates/dotnet/Package.Tests/Services/ServiceTests.cs.twig @@ -0,0 +1,210 @@ +{% import 'dotnet/base/utils.twig' as utils %} +{% macro generate_sub_dict(sub_def) %} +new Dictionary { +{% for subprop in sub_def.properties | filter(p => p.required) %} +{ "{{ subprop.name }}", {% if subprop.enum %}{{ subprop.enumName | caseUcfirst }}.{{ (subprop.enumKeys[0] ?? subprop.enum[0]) | caseEnumKey }}.Value{% elseif subprop.type == 'string' %}"{{ subprop['x-example'] | default('test') | escapeCsString }}"{% elseif subprop.type == 'integer' %}{{ subprop['x-example'] | default(1) }}{% elseif subprop.type == 'number' %}{{ subprop['x-example'] | default(1.0) }}{% elseif subprop.type == 'boolean' %}{% if subprop['x-example'] is defined %}{% if subprop['x-example'] is same as(true) or subprop['x-example'] == 'true' or subprop['x-example'] == 1 %}true{% else %}false{% endif %}{% else %}true{% endif %}{% elseif subprop.type == 'array' %}new List(){% elseif subprop.sub_schema %}new Dictionary(){% else %}"{{ subprop['x-example'] | default('test') | escapeCsString }}"{% endif %} }{% if not loop.last %},{% endif %} +{% endfor %} +} +{% endmacro %} +#pragma warning disable CS0618 // Type or member is obsolete +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Moq; +using {{ spec.title | caseUcfirst }}; +using {{ spec.title | caseUcfirst }}.Services; +{% if spec.definitions is not empty %} +using {{ spec.title | caseUcfirst }}.Models; +{% endif %} +{%- set hasEnums = spec.requestEnums is not empty -%} +{%- if hasEnums -%} +using {{ spec.title | caseUcfirst }}.Enums; +{% endif %} + +namespace {{ spec.title | caseUcfirst }}.Tests.Services +{ + public class {{ service.name | caseUcfirst }}Tests + { + private Mock _mockClient; + private {{ spec.title | caseUcfirst }}.Services.{{ service.name | caseUcfirst }} _{{ service.name | caseCamel }}; + + public {{ service.name | caseUcfirst }}Tests() + { + _mockClient = new Mock(); + _{{ service.name | caseCamel }} = new {{ spec.title | caseUcfirst }}.Services.{{ service.name | caseUcfirst }}(_mockClient.Object); + } + + [Fact] + public void Constructor_WithClient_CreatesInstance() + { + // Arrange + var client = new Mock().Object; + + // Act + var service = new {{ spec.title | caseUcfirst }}.Services.{{ service.name | caseUcfirst }}(client); + + // Assert + Assert.NotNull(service); + } + + {%~ for method in service.methods %} + [Fact] + public async Task {{ method.name | caseUcfirst }}_CallsClient() + { + // Arrange + {%~ if method.responseModel and method.responseModel != 'any' %} + var expectedResponse = new Dictionary + { + {%- for definition in spec.definitions ~%}{%~ if definition.name == method.responseModel -%}{%~ for property in definition.properties | filter((param) => param.required) ~%} + { "{{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(){% elseif property.type == 'object' and not property.sub_schema %}new Dictionary(){% elseif property.sub_schema %}{{ _self.generate_sub_dict(spec.definitions | filter(d => d.name == property.sub_schema) | first) }}{% else %}null{% endif %} }, + {%~ endfor ~%}{%- endif -%}{%~ endfor -%} + }; + {%~ elseif method.type == 'location' %} + var expectedResponse = new byte[] { 1, 2, 3 }; + {%~ elseif method.type == 'webAuth' %} + var expectedResponse = "success"; + {%~ else %} + var expectedResponse = new Dictionary(); + {%~ endif %} + + + {%~ if method.type == 'webAuth' %} + _mockClient.Setup(c => c.Redirect( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>() + )).ReturnsAsync(expectedResponse); + {%~ elseif 'multipart/form-data' in method.consumes %} + _mockClient.Setup(c => c.ChunkedUpload<{{ utils.resultType(spec.title, method) }}>( + It.IsAny(), + It.IsAny>(), + It.IsAny>(), + It.IsAny, {{ utils.resultType(spec.title, method) }}>>(), + It.IsAny(), + It.IsAny(), + It.IsAny>() + )).ReturnsAsync({% if method.responseModel and method.responseModel != 'any' %}{{ spec.title | caseUcfirst }}.Models.{{ method.responseModel | caseUcfirst | overrideIdentifier }}.From(expectedResponse){% else %}expectedResponse{% endif %}); + {%~ else %} + _mockClient.Setup(c => c.Call<{{ utils.resultType(spec.title, method) }}>( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>(){% if method.responseModel %}, + It.IsAny, {{ 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 %} + + // Act + {%~ if method.parameters.all | length > 0 %} + var result = await _{{ service.name | caseCamel }}.{{ method.name | caseUcfirst }}( + {%~ for parameter in method.parameters.all | filter((param) => param.required) ~%} + {{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(){% 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' %}{{parameter['x-example'] | default(1)}}{% elseif parameter.type == 'number' %}{{parameter['x-example'] | default(1.0)}}{% elseif parameter.type == 'string' %}"{% if parameter['x-example'] is not empty %}{{parameter['x-example']}}{% else %}test{% endif %}"{% else %}null{% endif %}{% if not loop.last %},{% endif %} + + {%~ endfor ~%} + ); + {%~ else %} + var result = await _{{ service.name | caseCamel }}.{{ method.name | caseUcfirst }}(); + {%~ endif %} + + // Assert + {%~ if method.responseModel and method.responseModel != 'any' %} + Assert.NotNull(result); + Assert.IsType<{{ spec.title | caseUcfirst }}.Models.{{ method.responseModel | caseUcfirst | overrideIdentifier }}>(result); + {%~ elseif method.type == 'location' %} + Assert.NotNull(result); + {%~ elseif method.type == 'webAuth' %} + Assert.NotNull(result); + Assert.Equal(expectedResponse, result); + {%~ endif %} + + {%~ if method.type == 'webAuth' %} + _mockClient.Verify(c => c.Redirect( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>() + ), Times.Once); + {%~ elseif 'multipart/form-data' in method.consumes %} + _mockClient.Verify(c => c.ChunkedUpload<{{ utils.resultType(spec.title, method) }}>( + It.IsAny(), + It.IsAny>(), + It.IsAny>(), + It.IsAny, {{ utils.resultType(spec.title, method) }}>>(), + It.IsAny(), + It.IsAny(), + It.IsAny>() + ), Times.Once); + {%~ else %} + _mockClient.Verify(c => c.Call<{{ utils.resultType(spec.title, method) }}>( + "{{ method.method | upper }}", + It.IsAny(), + It.IsAny>(), + It.IsAny>(){% if method.responseModel %}, + It.IsAny, {{ utils.resultType(spec.title, method) }}>>() + {% else %},null{% endif %} + ), Times.Once); + {%~ endif %} + } + + {%~ if method.parameters.all | filter((param) => param.required) | length > 0 %} + [Fact] + public async Task {{ method.name | caseUcfirst }}_WithParameters_PassesCorrectParameters() + { + // Arrange + {%~ for parameter in method.parameters.all | filter((param) => param.required) | slice(0, 3) ~%} + {% 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(){% 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 %}; + {%~ endfor ~%} + + {%~ if method.responseModel and method.responseModel != 'any' %} + var expectedResponse = new Dictionary + { + {%- for definition in spec.definitions ~%}{%~ if definition.name == method.responseModel -%}{%~ for property in definition.properties | filter((param) => param.required) ~%} + { "{{property.name}}", {% if property.enum %}{{ property.enumName | caseUcfirst }}.{{ (property.enumKeys[0] ?? property.enum[0]) | caseEnumKey }}.Value{% elseif property.type == 'string' %}"test"{% elseif property.type == 'integer' or property.type == 'number'%}1{% elseif property.type == 'boolean' %}true{% elseif property.type == 'array' %}new List(){% elseif property.type == 'object' and not property.sub_schema %}new Dictionary(){% elseif property.sub_schema %}{{ _self.generate_sub_dict(spec.definitions | filter(d => d.name == property.sub_schema) | first) }}{% else %}new Dictionary(){% endif %} }, + {%~ endfor ~%}{%- endif -%}{%~ endfor -%} + }; + {%~ elseif method.type == 'location' %} + var expectedResponse = new byte[] { 1, 2, 3 }; + {%~ else %} + var expectedResponse = new Dictionary(); + {%~ endif %} + + {%~ if not (method.type == 'webAuth' or 'multipart/form-data' in method.consumes) %} + _mockClient.Setup(c => c.Call<{{ utils.resultType(spec.title, method) }}>( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>(){% if method.responseModel %}, + It.IsAny, {{ 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 %} + + // Act + {%~ if method.parameters.all | length > 0 %} + await _{{ service.name | caseCamel }}.{{ method.name | caseUcfirst }}( + {%~ for parameter in method.parameters.all | filter((param) => param.required) ~%} + {{parameter.name | caseCamel | escapeKeyword}}: {% if loop.index0 < 3 %}{{parameter.name | caseCamel | escapeKeyword}}{% else %}{% 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(){% 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' %}1{% elseif parameter.type == 'string' %}"test"{% else %}null{% endif %}{% endif %}{% if not loop.last %},{% endif %} + + {%~ endfor ~%} + ); + {%~ endif %} + + // Assert - parameters were set correctly (implicitly tested by successful call) + Assert.True(true); + } + {%~ endif %} + + {%~ endfor %} + + [Fact] + public void Service_InheritsFromBaseService() + { + // Assert + Assert.IsAssignableFrom(_{{ service.name | caseCamel }}); + } + } +} diff --git a/templates/dotnet/Package.Tests/Tests.csproj.twig b/templates/dotnet/Package.Tests/Tests.csproj.twig new file mode 100644 index 0000000000..85145bb28e --- /dev/null +++ b/templates/dotnet/Package.Tests/Tests.csproj.twig @@ -0,0 +1,28 @@ + + + + net8.0 + {{ spec.title | caseUcfirst }}.Tests + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/templates/dotnet/Package.Tests/UploadProgressTests.cs.twig b/templates/dotnet/Package.Tests/UploadProgressTests.cs.twig new file mode 100644 index 0000000000..5d4de2c1f9 --- /dev/null +++ b/templates/dotnet/Package.Tests/UploadProgressTests.cs.twig @@ -0,0 +1,166 @@ +using Xunit; +using {{ spec.title | caseUcfirst }}; + +namespace {{ spec.title | caseUcfirst }}.Tests +{ + public class UploadProgressTests + { + [Fact] + public void Constructor_WithValidParameters_CreatesInstance() + { + // Arrange + var id = "test-id"; + var progress = 50.0; + var sizeUploaded = 1024L; + var chunksTotal = 10L; + var chunksUploaded = 5L; + + // Act + var uploadProgress = new UploadProgress(id, progress, sizeUploaded, chunksTotal, chunksUploaded); + + // Assert + Assert.NotNull(uploadProgress); + Assert.Equal(id, uploadProgress.Id); + Assert.Equal(progress, uploadProgress.Progress); + Assert.Equal(sizeUploaded, uploadProgress.SizeUploaded); + Assert.Equal(chunksTotal, uploadProgress.ChunksTotal); + Assert.Equal(chunksUploaded, uploadProgress.ChunksUploaded); + } + + [Fact] + public void Constructor_WithZeroProgress_CreatesInstance() + { + // Arrange & Act + var uploadProgress = new UploadProgress("id", 0.0, 0L, 10L, 0L); + + // Assert + Assert.Equal(0.0, uploadProgress.Progress); + Assert.Equal(0L, uploadProgress.SizeUploaded); + Assert.Equal(0L, uploadProgress.ChunksUploaded); + } + + [Fact] + public void Constructor_WithCompleteProgress_CreatesInstance() + { + // Arrange & Act + var uploadProgress = new UploadProgress("id", 100.0, 5120L, 5L, 5L); + + // Assert + Assert.Equal(100.0, uploadProgress.Progress); + Assert.Equal(5L, uploadProgress.ChunksTotal); + Assert.Equal(5L, uploadProgress.ChunksUploaded); + } + + [Fact] + public void Id_IsReadOnly() + { + // Arrange + var uploadProgress = new UploadProgress("test-id", 50.0, 1024L, 10L, 5L); + + // Assert + var propertyInfo = typeof(UploadProgress).GetProperty("Id"); + Assert.NotNull(propertyInfo); + Assert.Null(propertyInfo.GetSetMethod()); + } + + [Fact] + public void Progress_IsReadOnly() + { + // Arrange + var uploadProgress = new UploadProgress("test-id", 50.0, 1024L, 10L, 5L); + + // Assert + var propertyInfo = typeof(UploadProgress).GetProperty("Progress"); + Assert.NotNull(propertyInfo); + Assert.Null(propertyInfo.GetSetMethod()); + } + + [Fact] + public void SizeUploaded_IsReadOnly() + { + // Arrange + var uploadProgress = new UploadProgress("test-id", 50.0, 1024L, 10L, 5L); + + // Assert + var propertyInfo = typeof(UploadProgress).GetProperty("SizeUploaded"); + Assert.NotNull(propertyInfo); + Assert.Null(propertyInfo.GetSetMethod()); + } + + [Fact] + public void ChunksTotal_IsReadOnly() + { + // Arrange + var uploadProgress = new UploadProgress("test-id", 50.0, 1024L, 10L, 5L); + + // Assert + var propertyInfo = typeof(UploadProgress).GetProperty("ChunksTotal"); + Assert.NotNull(propertyInfo); + Assert.Null(propertyInfo.GetSetMethod()); + } + + [Fact] + public void ChunksUploaded_IsReadOnly() + { + // Arrange + var uploadProgress = new UploadProgress("test-id", 50.0, 1024L, 10L, 5L); + + // Assert + var propertyInfo = typeof(UploadProgress).GetProperty("ChunksUploaded"); + Assert.NotNull(propertyInfo); + Assert.Null(propertyInfo.GetSetMethod()); + } + + [Fact] + public void Progress_WithDecimalValue_StoresCorrectly() + { + // Arrange & Act + var uploadProgress = new UploadProgress("id", 75.5, 3840L, 10L, 7L); + + // Assert + Assert.Equal(75.5, uploadProgress.Progress); + } + + [Fact] + public void SizeUploaded_WithLargeValue_StoresCorrectly() + { + // Arrange + var largeSize = long.MaxValue; + + // Act + var uploadProgress = new UploadProgress("id", 100.0, largeSize, 1000L, 1000L); + + // Assert + Assert.Equal(largeSize, uploadProgress.SizeUploaded); + } + + [Fact] + public void ChunksTotal_MatchesChunksUploaded_WhenComplete() + { + // Arrange & Act + var uploadProgress = new UploadProgress("id", 100.0, 10240L, 10L, 10L); + + // Assert + Assert.Equal(uploadProgress.ChunksTotal, uploadProgress.ChunksUploaded); + } + + [Theory] + [InlineData("id1", 25.0, 256L, 4L, 1L)] + [InlineData("id2", 50.0, 512L, 4L, 2L)] + [InlineData("id3", 75.0, 768L, 4L, 3L)] + [InlineData("id4", 100.0, 1024L, 4L, 4L)] + public void Constructor_WithVariousValues_CreatesCorrectInstance( + string id, double progress, long sizeUploaded, long chunksTotal, long chunksUploaded) + { + // Act + var uploadProgress = new UploadProgress(id, progress, sizeUploaded, chunksTotal, chunksUploaded); + + // Assert + Assert.Equal(id, uploadProgress.Id); + Assert.Equal(progress, uploadProgress.Progress); + Assert.Equal(sizeUploaded, uploadProgress.SizeUploaded); + Assert.Equal(chunksTotal, uploadProgress.ChunksTotal); + Assert.Equal(chunksUploaded, uploadProgress.ChunksUploaded); + } + } +} diff --git a/templates/dotnet/Package.sln b/templates/dotnet/Package.sln index a8e4b4e574..c4ffeb4bde 100644 --- a/templates/dotnet/Package.sln +++ b/templates/dotnet/Package.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.30114.128 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Appwrite", "Appwrite\Appwrite.csproj", "{ABD3EB63-B648-49D6-B7FD-C17A762A3EC3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Appwrite.Tests", "Appwrite.Tests\Appwrite.Tests.csproj", "{B9B36337-BF75-4601-AB9C-C2A7ACC91DF6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,8 @@ Global {ABD3EB63-B648-49D6-B7FD-C17A762A3EC3}.Debug|Any CPU.Build.0 = Debug|Any CPU {ABD3EB63-B648-49D6-B7FD-C17A762A3EC3}.Release|Any CPU.ActiveCfg = Release|Any CPU {ABD3EB63-B648-49D6-B7FD-C17A762A3EC3}.Release|Any CPU.Build.0 = Release|Any CPU + {B9B36337-BF75-4601-AB9C-C2A7ACC91DF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9B36337-BF75-4601-AB9C-C2A7ACC91DF6}.Debug|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/templates/dotnet/Package/Client.cs.twig b/templates/dotnet/Package/Client.cs.twig index 53bd7ba62c..cc624a7368 100644 --- a/templates/dotnet/Package/Client.cs.twig +++ b/templates/dotnet/Package/Client.cs.twig @@ -88,6 +88,27 @@ namespace {{ spec.title | caseUcfirst }} } } + // Parameterless constructor required for mocking frameworks (Moq/Castle) + // Initializes minimal defaults so proxies can be created without errors. + protected Client() + { + _endpoint = "{{spec.endpoint}}"; + _http = new HttpClient(); + _httpForRedirect = new HttpClient(new HttpClientHandler(){ AllowAutoRedirect = false }); + + _headers = new Dictionary() + { + { "content-type", "application/json" }, + { "user-agent" , $"{{spec.title | caseUcfirst}}{{ language.name | caseUcfirst }}SDK/{{ sdk.version }} ({Environment.OSVersion.Platform}; {Environment.OSVersion.VersionString})"}, + { "x-sdk-name", "{{ sdk.name }}" }, + { "x-sdk-platform", "{{ sdk.platform }}" }, + { "x-sdk-language", "{{ language.name | caseLower }}" }, + { "x-sdk-version", "{{ sdk.version }}" } + }; + + _config = new Dictionary(); + } + public Client SetSelfSigned(bool selfSigned) { var handler = new HttpClientHandler() @@ -154,6 +175,7 @@ namespace {{ spec.title | caseUcfirst }} foreach (var parameter in parameters) { + if (parameter.Value == null) continue; if (parameter.Key == "file") { var fileContent = parameters["file"] as MultipartFormDataContent; @@ -222,7 +244,7 @@ namespace {{ spec.title | caseUcfirst }} return request; } - public async Task Redirect( + public virtual async Task Redirect( string method, string path, Dictionary headers, @@ -268,7 +290,7 @@ namespace {{ spec.title | caseUcfirst }} return response.Headers.Location?.OriginalString ?? string.Empty; } - public Task> Call( + public virtual Task> Call( string method, string path, Dictionary headers, @@ -277,7 +299,7 @@ namespace {{ spec.title | caseUcfirst }} return Call>(method, path, headers, parameters); } - public async Task Call( + public virtual async Task Call( string method, string path, Dictionary headers, @@ -352,7 +374,7 @@ namespace {{ spec.title | caseUcfirst }} } } - public async Task ChunkedUpload( + public virtual async Task ChunkedUpload( string path, Dictionary headers, Dictionary parameters, diff --git a/templates/dotnet/Package/Converters/ObjectToInferredTypesConverter.cs.twig b/templates/dotnet/Package/Converters/ObjectToInferredTypesConverter.cs.twig index 563f92992a..ce772c93df 100644 --- a/templates/dotnet/Package/Converters/ObjectToInferredTypesConverter.cs.twig +++ b/templates/dotnet/Package/Converters/ObjectToInferredTypesConverter.cs.twig @@ -7,32 +7,60 @@ namespace {{ spec.title | caseUcfirst }}.Converters { public class ObjectToInferredTypesConverter : JsonConverter { - public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - switch (reader.TokenType) + using (JsonDocument document = JsonDocument.ParseValue(ref reader)) { - case JsonTokenType.True: - return true; - case JsonTokenType.False: - return false; - case JsonTokenType.Number: - if (reader.TryGetInt64(out long l)) + return ConvertElement(document.RootElement); + } + } + + private object? ConvertElement(JsonElement element) + { + switch (element.ValueKind) + { + case JsonValueKind.Object: + var dictionary = new Dictionary(); + foreach (var property in element.EnumerateObject()) { - return l; + dictionary[property.Name] = ConvertElement(property.Value); + } + return dictionary; + + case JsonValueKind.Array: + var list = new List(); + foreach (var item in element.EnumerateArray()) + { + list.Add(ConvertElement(item)); } - return reader.GetDouble(); - case JsonTokenType.String: - if (reader.TryGetDateTime(out DateTime datetime)) + return list; + + case JsonValueKind.String: + if (element.TryGetDateTime(out DateTime datetime)) { return datetime; } - return reader.GetString()!; - case JsonTokenType.StartObject: - return JsonSerializer.Deserialize>(ref reader, options)!; - case JsonTokenType.StartArray: - return JsonSerializer.Deserialize(ref reader, options)!; + return element.GetString(); + + case JsonValueKind.Number: + if (element.TryGetInt64(out long l)) + { + return l; + } + return element.GetDouble(); + + case JsonValueKind.True: + return true; + + case JsonValueKind.False: + return false; + + case JsonValueKind.Null: + case JsonValueKind.Undefined: + return null; + default: - return JsonDocument.ParseValue(ref reader).RootElement.Clone(); + throw new JsonException($"Unsupported JsonValueKind: {element.ValueKind}"); } } diff --git a/templates/dotnet/Package/Exception.cs.twig b/templates/dotnet/Package/Exception.cs.twig index e78d78c2cc..31d9c70adc 100644 --- a/templates/dotnet/Package/Exception.cs.twig +++ b/templates/dotnet/Package/Exception.cs.twig @@ -18,10 +18,10 @@ namespace {{spec.title | caseUcfirst}} this.Type = type; this.Response = response; } + public {{spec.title | caseUcfirst}}Exception(string message, Exception inner) : base(message, inner) { } } } - diff --git a/templates/dotnet/Package/Extensions/Extensions.cs.twig b/templates/dotnet/Package/Extensions/Extensions.cs.twig index 0ac19f7ce7..4fe485e1c6 100644 --- a/templates/dotnet/Package/Extensions/Extensions.cs.twig +++ b/templates/dotnet/Package/Extensions/Extensions.cs.twig @@ -13,15 +13,14 @@ namespace {{ spec.title | caseUcfirst }}.Extensions return JsonSerializer.Serialize(dict, Client.SerializerOptions); } - public static List ConvertToList(this object value) + public static IEnumerable ToEnumerable(this object value) { return value switch { - JsonElement jsonElement => jsonElement.Deserialize>() ?? throw new InvalidCastException($"Cannot deserialize {jsonElement} to List<{typeof(T)}>."), - object[] objArray => objArray.Cast().ToList(), - List list => list, - IEnumerable enumerable => enumerable.ToList(), - _ => throw new InvalidCastException($"Cannot convert {value.GetType()} to List<{typeof(T)}>") + object[] array => array, + IEnumerable enumerable => enumerable, + IEnumerable nonGeneric => nonGeneric.Cast(), + _ => throw new InvalidCastException($"Cannot convert {value?.GetType().Name ?? "null"} to IEnumerable") }; } @@ -637,4 +636,4 @@ namespace {{ spec.title | caseUcfirst }}.Extensions return GetMimeTypeFromExtension(System.IO.Path.GetExtension(path)); } } -} \ No newline at end of file +} diff --git a/templates/dotnet/Package/Models/InputFile.cs.twig b/templates/dotnet/Package/Models/InputFile.cs.twig index 241a3adad5..aaf7a66202 100644 --- a/templates/dotnet/Package/Models/InputFile.cs.twig +++ b/templates/dotnet/Package/Models/InputFile.cs.twig @@ -1,5 +1,5 @@ using System.IO; -using Appwrite.Extensions; +using {{ spec.title | caseUcfirst }}.Extensions; namespace {{ spec.title | caseUcfirst }}.Models { @@ -38,4 +38,4 @@ namespace {{ spec.title | caseUcfirst }}.Models SourceType = "bytes" }; } -} \ No newline at end of file +} diff --git a/templates/dotnet/Package/Models/Model.cs.twig b/templates/dotnet/Package/Models/Model.cs.twig index e3f0bd132d..2ff72fac9e 100644 --- a/templates/dotnet/Package/Models/Model.cs.twig +++ b/templates/dotnet/Package/Models/Model.cs.twig @@ -1,28 +1,30 @@ - +{% set DefinitionClass = definition.name | caseUcfirst | overrideIdentifier %} using System; using System.Linq; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; +{% if definition.properties | filter(p => p.enum) | length > 0 %} using {{ spec.title | caseUcfirst }}.Enums; +{% endif %} using {{ spec.title | caseUcfirst }}.Extensions; namespace {{ spec.title | caseUcfirst }}.Models { - public class {{ definition.name | caseUcfirst | overrideIdentifier }} + public class {{ DefinitionClass }} { {%~ for property in definition.properties %} [JsonPropertyName("{{ property.name }}")] - public {{ sub_schema(property) | raw }} {{ property_name(definition, property) | overrideProperty(definition.name) }} { get; private set; } + public {{ sub_schema(property) }} {{ property_name(definition, property) | overrideProperty(definition.name) }} { get; private set; } {%~ endfor %} {%~ if definition.additionalProperties %} public Dictionary Data { get; private set; } {%~ endif %} - public {{ definition.name | caseUcfirst | overrideIdentifier }}( + public {{ DefinitionClass }}( {%~ for property in definition.properties %} - {{ sub_schema(property) | raw }} {{ property.name | caseCamel | escapeKeyword }}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} + {{ sub_schema(property) }} {{ property.name | caseCamel | escapeKeyword }}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} {%~ endfor %} {%~ if definition.additionalProperties %} @@ -37,45 +39,14 @@ namespace {{ spec.title | caseUcfirst }}.Models {%~ endif %} } - public static {{ definition.name | caseUcfirst | overrideIdentifier }} From(Dictionary map) => new {{ definition.name | caseUcfirst | overrideIdentifier }}( + public static {{ DefinitionClass }} From(Dictionary map) => new {{ DefinitionClass }}( {%~ for property in definition.properties %} + {%~ set v = 'v' ~ loop.index0 %} + {%~ set mapAccess = 'map["' ~ property.name ~ '"]' %} {{ property.name | caseCamel | escapeKeyword | removeDollarSign }}:{{' '}} - {%- if property.sub_schema %} - {%- if property.type == 'array' -%} - map["{{ property.name }}"].ConvertToList>().Select(it => {{ property.sub_schema | caseUcfirst | overrideIdentifier }}.From(map: it)).ToList() - {%- else -%} - {{ property.sub_schema | caseUcfirst | overrideIdentifier }}.From(map: map["{{ property.name }}"] is JsonElement jsonObj{{ loop.index }} ? jsonObj{{ loop.index }}.Deserialize>()! : (Dictionary)map["{{ property.name }}"]) - {%- endif %} - {%- elseif property.enum %} - {%- set enumName = property['enumName'] ?? property.name -%} - {%- if not property.required -%} - map.TryGetValue("{{ property.name }}", out var enumRaw{{ loop.index }}) - ? enumRaw{{ loop.index }} == null - ? null - : new {{ enumName | caseUcfirst }}(enumRaw{{ loop.index }}.ToString()!) - : null - {%- else -%} - new {{ enumName | caseUcfirst }}(map["{{ property.name }}"].ToString()!) - {%- endif %} - {%- else %} - {%- if property.type == 'array' -%} - map["{{ property.name }}"].ConvertToList<{{ property | typeName | replace({'List<': '', '>': ''}) }}>() - {%- else %} - {%- if property.type == "integer" or property.type == "number" %} - {%- if not property.required -%}map["{{ property.name }}"] == null ? null :{% endif %}Convert.To{% if property.type == "integer" %}Int64{% else %}Double{% endif %}(map["{{ property.name }}"]) - {%- else %} - {%- if property.type == "boolean" -%} - ({{ property | typeName }}{% if not property.required %}?{% endif %})map["{{ property.name }}"] - {%- else %} - {%- if not property.required -%} - map.TryGetValue("{{ property.name }}", out var {{ property.name | caseCamel | escapeKeyword | removeDollarSign }}) ? {{ property.name | caseCamel | escapeKeyword | removeDollarSign }}?.ToString() : null - {%- else -%} - map["{{ property.name }}"].ToString() - {%- endif %} - {%- endif %} - {%~ endif %} - {%~ endif %} - {%~ endif %} + {%- if not property.required -%}map.TryGetValue("{{ property.name }}", out var {{ v }}) ? {% endif -%} +{{ parse_value(property, mapAccess, v) }} + {%- if not property.required %} : null{% endif -%} {%- if not loop.last or (loop.last and definition.additionalProperties) %}, {%~ endif %} {%~ endfor %} diff --git a/templates/dotnet/Package/Models/UploadProgress.cs.twig b/templates/dotnet/Package/Models/UploadProgress.cs.twig index 47c78391ce..ee6fb58ba3 100644 --- a/templates/dotnet/Package/Models/UploadProgress.cs.twig +++ b/templates/dotnet/Package/Models/UploadProgress.cs.twig @@ -23,4 +23,4 @@ namespace {{ spec.title | caseUcfirst }} ChunksUploaded = chunksUploaded; } } -} \ No newline at end of file +} diff --git a/templates/dotnet/Package/Operator.cs.twig b/templates/dotnet/Package/Operator.cs.twig index 022b209140..f47202409b 100644 --- a/templates/dotnet/Package/Operator.cs.twig +++ b/templates/dotnet/Package/Operator.cs.twig @@ -79,7 +79,7 @@ namespace {{ spec.title | caseUcfirst }} } } - override public string ToString() + public override string ToString() { return JsonSerializer.Serialize(this, Client.SerializerOptions); } diff --git a/templates/dotnet/Package/Query.cs.twig b/templates/dotnet/Package/Query.cs.twig index 3cd431da90..aa8bc1ab09 100644 --- a/templates/dotnet/Package/Query.cs.twig +++ b/templates/dotnet/Package/Query.cs.twig @@ -41,7 +41,7 @@ namespace {{ spec.title | caseUcfirst }} } } - override public string ToString() + public override string ToString() { return JsonSerializer.Serialize(this, Client.SerializerOptions); } @@ -274,4 +274,4 @@ namespace {{ spec.title | caseUcfirst }} return new Query("notTouches", attribute, new List { values }).ToString(); } } -} \ No newline at end of file +} diff --git a/templates/dotnet/Package/Role.cs.twig b/templates/dotnet/Package/Role.cs.twig index b3ecf2610b..3c7b2b33f3 100644 --- a/templates/dotnet/Package/Role.cs.twig +++ b/templates/dotnet/Package/Role.cs.twig @@ -1,4 +1,4 @@ -namespace Appwrite +namespace {{ spec.title | caseUcfirst }} { /// /// Helper class to generate role strings for Permission. @@ -89,4 +89,4 @@ namespace Appwrite return $"label:{name}"; } } -} \ No newline at end of file +} diff --git a/templates/dotnet/Package/Services/ServiceTemplate.cs.twig b/templates/dotnet/Package/Services/ServiceTemplate.cs.twig index 811078dbd6..ea96412674 100644 --- a/templates/dotnet/Package/Services/ServiceTemplate.cs.twig +++ b/templates/dotnet/Package/Services/ServiceTemplate.cs.twig @@ -1,5 +1,4 @@ {% import 'dotnet/base/utils.twig' as utils %} - using System; using System.Collections.Generic; using System.Linq; diff --git a/templates/dotnet/base/utils.twig b/templates/dotnet/base/utils.twig index 19ea870059..7ebb547a66 100644 --- a/templates/dotnet/base/utils.twig +++ b/templates/dotnet/base/utils.twig @@ -12,5 +12,5 @@ {% if (method.type == "webAuth" or method.type == "location") and method.auth|length > 0 %}{{ true }}{% else %}{{false}}{% endif %} {% endmacro %} {% macro resultType(namespace, method) %} -{% if method.type == "webAuth" %}bool{% elseif method.type == "location" %}byte[]{% elseif not method.responseModel or method.responseModel == 'any' %}object{% else %}Models.{{method.responseModel | caseUcfirst | overrideIdentifier }}{% endif %} +{% if method.type == "webAuth" %}bool{% elseif method.type == "location" %}byte[]{% elseif not method.responseModel or method.responseModel == 'any' %}object{% else %}Appwrite.Models.{{method.responseModel | caseUcfirst | overrideIdentifier }}{% endif %} {% endmacro %} \ No newline at end of file