From 98f92b8cf7b001737758a9bc0c73095e07477a37 Mon Sep 17 00:00:00 2001 From: EvilKot Date: Wed, 16 Oct 2024 10:39:13 +0300 Subject: [PATCH] feat(screenshots): add the stringIds parameter support (#271) --- src/Crowdin.Api/Core/InternalExtensions.cs | 55 ++++- .../Screenshots/ScreenshotsApiExecutor.cs | 18 +- .../Core/InternalExtensionsTests.cs | 202 ++++++++++++++++++ .../Core/Resources/Screenshots.Designer.cs | 18 +- .../Core/Resources/Screenshots.resx | 80 +++++++ .../Screenshots/ScreenshotsApiTests.cs | 50 ++++- .../SourceStrings/SourceStringsApiTests.cs | 46 ++-- 7 files changed, 425 insertions(+), 44 deletions(-) create mode 100644 tests/Crowdin.Api.Tests/Core/InternalExtensionsTests.cs diff --git a/src/Crowdin.Api/Core/InternalExtensions.cs b/src/Crowdin.Api/Core/InternalExtensions.cs index ee8601e1..1a6f249a 100644 --- a/src/Crowdin.Api/Core/InternalExtensions.cs +++ b/src/Crowdin.Api/Core/InternalExtensions.cs @@ -1,4 +1,4 @@ - + using System; using System.Collections.Generic; using System.ComponentModel; @@ -26,7 +26,7 @@ internal static string ToDescriptionString(this Enum? path) .GetType() .GetField(path.ToString()) .GetCustomAttribute(false); - + return attribute != null ? attribute.Description : string.Empty; } @@ -42,7 +42,7 @@ internal static void AddParamIfPresent(this IDictionary queryPar queryParams.AddParamIfPresent(key, value.ToString()); } } - + internal static void AddParamIfPresent(this IDictionary queryParams, string key, long? value) { if (value.HasValue) @@ -50,7 +50,7 @@ internal static void AddParamIfPresent(this IDictionary queryPar queryParams.AddParamIfPresent(key, value.ToString()); } } - + internal static void AddParamIfPresent(this IDictionary queryParams, string key, bool? value) { if (value.HasValue) @@ -75,6 +75,46 @@ internal static void AddParamIfPresent(this IDictionary queryPar } } + internal static void AddParamIfPresent(this IDictionary queryParams, string key, IEnumerable? values) + { + if (values != null && values.Any()) + { + queryParams.Add(key, string.Join(",", values.Select(value => value.ToString()))); + } + } + + internal static void AddParamIfPresent(this IDictionary queryParams, string key, IEnumerable? values) + { + if (values != null && values.Any()) + { + queryParams.Add(key, string.Join(",", values.Select(value => value.ToString()))); + } + } + + internal static void AddParamIfPresent(this IDictionary queryParams, string key, IEnumerable? values) + { + if (values != null && values.Any()) + { + queryParams.Add(key, string.Join(",", values.Select(value => value.ToString().ToLower()))); + } + } + + internal static void AddParamIfPresent(this IDictionary queryParams, string key, IEnumerable? values) + { + if (values != null && values.Any()) + { + queryParams.Add(key, string.Join(",", values.Select(value => value.ToString()))); + } + } + + internal static void AddParamIfPresent(this IDictionary queryParams, string key, IEnumerable? values) + { + if (values != null && values.Any()) + { + queryParams.Add(key, string.Join(",", values)); + } + } + internal static void AddDescriptionEnumValueIfPresent( this IDictionary queryParams, string key, TEnum? enumMember) where TEnum : struct, Enum @@ -84,15 +124,12 @@ internal static void AddDescriptionEnumValueIfPresent( queryParams.Add(key, enumMember.Value.ToDescriptionString()); } } - + internal static void AddSortingRulesIfPresent( this IDictionary queryParams, IEnumerable? sortingRules) { - if (sortingRules != null && sortingRules.Any()) - { - queryParams.Add("orderBy", string.Join(",", sortingRules.Select(rule => rule.ToString()))); - } + AddParamIfPresent(queryParams, "orderBy", sortingRules); } } } \ No newline at end of file diff --git a/src/Crowdin.Api/Screenshots/ScreenshotsApiExecutor.cs b/src/Crowdin.Api/Screenshots/ScreenshotsApiExecutor.cs index e622ca53..94f21d96 100644 --- a/src/Crowdin.Api/Screenshots/ScreenshotsApiExecutor.cs +++ b/src/Crowdin.Api/Screenshots/ScreenshotsApiExecutor.cs @@ -1,4 +1,4 @@ - + using System.Collections.Generic; using System.Net; using System.Threading.Tasks; @@ -28,7 +28,7 @@ public ScreenshotsApiExecutor(ICrowdinApiClient apiClient, IJsonParser jsonParse } #region Screenshots - + /// /// List screenshots. Documentation: /// Crowdin API @@ -39,12 +39,14 @@ public async Task> ListScreenshots( int projectId, int limit = 25, int offset = 0, - IEnumerable? orderBy = null) + IEnumerable? orderBy = null, + IEnumerable? stringIds = null) { string url = FormUrl_Screenshots(projectId); IDictionary queryParams = Utils.CreateQueryParamsFromPaging(limit, offset); queryParams.AddSortingRulesIfPresent(orderBy); - + queryParams.AddParamIfPresent("stringIds", stringIds); + CrowdinApiResult result = await _apiClient.SendGetRequest(url, queryParams); return _jsonParser.ParseResponseList(result.JsonObject); } @@ -113,7 +115,7 @@ public async Task EditScreenshot(int projectId, int screenshotId, IE CrowdinApiResult result = await _apiClient.SendPatchRequest(url, patches); return _jsonParser.ParseResponseObject(result.JsonObject); } - + #region Helper methods private static string FormUrl_Screenshots(int projectId) @@ -142,11 +144,11 @@ public async Task> ListTags(int projectId, int screenshotId, i { string url = FormUrl_ScreenshotId(projectId, screenshotId); IDictionary queryParams = Utils.CreateQueryParamsFromPaging(limit, offset); - + CrowdinApiResult result = await _apiClient.SendGetRequest(url, queryParams); return _jsonParser.ParseResponseList(result.JsonObject); } - + /// /// Replace tags. Documentation: /// Crowdin API @@ -163,7 +165,7 @@ public async Task ReplaceTags(int projectId, int screenshotId, IEnumerable /// Replace tags. Documentation: /// Crowdin API diff --git a/tests/Crowdin.Api.Tests/Core/InternalExtensionsTests.cs b/tests/Crowdin.Api.Tests/Core/InternalExtensionsTests.cs new file mode 100644 index 00000000..5d5a9a4e --- /dev/null +++ b/tests/Crowdin.Api.Tests/Core/InternalExtensionsTests.cs @@ -0,0 +1,202 @@ + +using System.Collections.Generic; + +using Crowdin.Api.Core; + +using Moq; +using Xunit; + +namespace Crowdin.Api.Tests.Core +{ + public class InternalExtensionsTests + { + [Fact] + public void ToQueryString() + { + var queryParams = new Dictionary + { + { "limit", "25" }, + { "offset", "0" } + }; + + var queryString = queryParams.ToQueryString(); + + Assert.Equal("limit=25&offset=0", queryString); + } + + [Fact] + public void AddParamIfPresentInt() + { + var queryParams = new Dictionary(); + + queryParams.AddParamIfPresent("param", 1); + queryParams.AddParamIfPresent("other", (int?) null); + + Assert.Single(queryParams.Keys); + Assert.True(queryParams.TryGetValue("param", out string? value)); + Assert.Equal("1", value); + } + + [Fact] + public void AddParamIfPresentLong() + { + var queryParams = new Dictionary(); + + queryParams.AddParamIfPresent("param", 1L); + queryParams.AddParamIfPresent("other", (long?) null); + + Assert.Single(queryParams.Keys); + Assert.True(queryParams.TryGetValue("param", out string? value)); + Assert.Equal("1", value); + } + + [Fact] + public void AddParamIfPresentBool() + { + var queryParams = new Dictionary(); + + queryParams.AddParamIfPresent("param", true); + queryParams.AddParamIfPresent("other", (bool?) null); + + Assert.Single(queryParams.Keys); + Assert.True(queryParams.TryGetValue("param", out string? value)); + Assert.Equal("true", value); + } + + [Fact] + public void AddParamIfPresentObject() + { + Mock mockObject = new Mock(); + mockObject.Setup(o => o.ToString()).Returns("test"); + + var queryParams = new Dictionary(); + + queryParams.AddParamIfPresent("param", mockObject.Object); + queryParams.AddParamIfPresent("other", null as object); + + Assert.Single(queryParams.Keys); + Assert.True(queryParams.TryGetValue("param", out string? value)); + Assert.Equal("test", value); + } + + [Fact] + public void AddParamIfPresentString() + { + var queryParams = new Dictionary(); + + queryParams.AddParamIfPresent("param", "test"); + queryParams.AddParamIfPresent("other1", null as string); + queryParams.AddParamIfPresent("other2", ""); + queryParams.AddParamIfPresent("other3", " "); + + Assert.Single(queryParams.Keys); + Assert.True(queryParams.TryGetValue("param", out string? value)); + Assert.Equal("test", value); + } + + [Fact] + public void AddParamListIfPresentInt() + { + var queryParams = new Dictionary(); + + queryParams.AddParamIfPresent("param", new int[] { 1, 2, 3 }); + queryParams.AddParamIfPresent("other1", new int[] { }); + queryParams.AddParamIfPresent("other2", null as int[]); + + Assert.Single(queryParams.Keys); + Assert.True(queryParams.TryGetValue("param", out string? value)); + Assert.Equal("1,2,3", value); + } + + [Fact] + public void AddParamListIfPresentLong() + { + var queryParams = new Dictionary(); + + queryParams.AddParamIfPresent("param", new long[] { 1, 2, 3 }); + queryParams.AddParamIfPresent("other1", new long[] { }); + queryParams.AddParamIfPresent("other2", null as long[]); + + Assert.Single(queryParams.Keys); + Assert.True(queryParams.TryGetValue("param", out string? value)); + Assert.Equal("1,2,3", value); + } + + [Fact] + public void AddParamListIfPresentBool() + { + var queryParams = new Dictionary(); + + queryParams.AddParamIfPresent("param", new bool[] { true, false, true }); + queryParams.AddParamIfPresent("other1", new bool[] { }); + queryParams.AddParamIfPresent("other2", null as bool[]); + + Assert.Single(queryParams.Keys); + Assert.True(queryParams.TryGetValue("param", out string? value)); + Assert.Equal("true,false,true", value); + } + + [Fact] + public void AddParamListIfPresentObject() + { + Mock mockObject1 = new Mock(); + Mock mockObject2 = new Mock(); + Mock mockObject3 = new Mock(); + + mockObject1.Setup(o => o.ToString()).Returns("test1"); + mockObject2.Setup(o => o.ToString()).Returns("test2"); + mockObject3.Setup(o => o.ToString()).Returns("test3"); + + var queryParams = new Dictionary(); + + queryParams.AddParamIfPresent("param", new object[] { + mockObject1.Object, + mockObject2.Object, + mockObject3.Object + }); + queryParams.AddParamIfPresent("other1", new object[] {}); + queryParams.AddParamIfPresent("other2", null as object[]); + + Assert.Single(queryParams.Keys); + Assert.True(queryParams.TryGetValue("param", out string? value)); + Assert.Equal("test1,test2,test3", value); + } + + [Fact] + public void AddParamListIfPresentString() + { + var queryParams = new Dictionary(); + + queryParams.AddParamIfPresent("param", new string[] {"test1", "test2", "test3"}); + queryParams.AddParamIfPresent("other1", new string[] {}); + queryParams.AddParamIfPresent("other2", null as string[]); + + Assert.Single(queryParams.Keys); + Assert.True(queryParams.TryGetValue("param", out string? value)); + Assert.Equal("test1,test2,test3", value); + } + + [Fact] + public void AddSortingRulesIfPresent() + { + var sortingRules1 = new SortingRule[] + { + new SortingRule() { Field = "createdAt", Order = SortingOrder.Descending }, + new SortingRule() { Field = "name", Order = SortingOrder.Ascending }, + }; + + var sortingRules2 = new SortingRule[] { }; + var sortingRules3 = null as SortingRule[]; + + var queryParams = new Dictionary(); + + queryParams.AddSortingRulesIfPresent(sortingRules1); + queryParams.AddSortingRulesIfPresent(sortingRules2); + queryParams.AddSortingRulesIfPresent(sortingRules3); + + Assert.Single(queryParams.Keys); + Assert.True(queryParams.TryGetValue("orderBy", out string? value)); + Assert.Equal("createdAt desc,name asc", value); + } + } +} \ No newline at end of file diff --git a/tests/Crowdin.Api.Tests/Core/Resources/Screenshots.Designer.cs b/tests/Crowdin.Api.Tests/Core/Resources/Screenshots.Designer.cs index 5d6f962f..ccc78d60 100644 --- a/tests/Crowdin.Api.Tests/Core/Resources/Screenshots.Designer.cs +++ b/tests/Crowdin.Api.Tests/Core/Resources/Screenshots.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // @@ -62,5 +62,21 @@ internal static string EditScreenshot_Response { return ResourceManager.GetString("EditScreenshot_Response", resourceCulture); } } + + internal static string GetScreenshot_Response + { + get + { + return ResourceManager.GetString("GetScreenshot_Response", resourceCulture); + } + } + + internal static string ListScreenshots_Response + { + get + { + return ResourceManager.GetString("ListScreenshots_Response", resourceCulture); + } + } } } diff --git a/tests/Crowdin.Api.Tests/Core/Resources/Screenshots.resx b/tests/Crowdin.Api.Tests/Core/Resources/Screenshots.resx index fea9fc09..b6d58d26 100644 --- a/tests/Crowdin.Api.Tests/Core/Resources/Screenshots.resx +++ b/tests/Crowdin.Api.Tests/Core/Resources/Screenshots.resx @@ -96,4 +96,84 @@ } } + + { + "data": { + "id": 2, + "userId": 6, + "url": "https://production-enterprise-screenshots.downloads.crowdin.com/992000002/6/2/middle.jpg?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIGJKLQV66ZXPMMEA%2F20190923%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20190923T093016Z&X-Amz-SignedHeaders=host&X-Amz-Expires=120&X-Amz-Signature=8df06f57594f7d1804b7c037629f6916224415e9b935c4f6619fbe002fb25e73", + "name": "translate_with_siri.jpg", + "size": { + "width": 267, + "height": 176 + }, + "tagsCount": 0, + "tags": [ + { + "id": 98, + "screenshotId": 2, + "stringId": 2822, + "position": { + "x": 474, + "y": 147, + "width": 490, + "height": 99 + }, + "createdAt": "2019-09-23T09:35:31+00:00" + } + ], + "labelIds": [ + 0, 1 + ], + "createdAt": "2019-09-23T09:29:19+00:00", + "updatedAt": "2019-09-23T09:29:19+00:00" + } +} + + + { + "data": [ + { + "data": { + "id": 2, + "userId": 6, + "url": "https://production-enterprise-screenshots.downloads.crowdin.com/992000002/6/2/middle.jpg?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIGJKLQV66ZXPMMEA%2F20190923%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20190923T093016Z&X-Amz-SignedHeaders=host&X-Amz-Expires=120&X-Amz-Signature=8df06f57594f7d1804b7c037629f6916224415e9b935c4f6619fbe002fb25e73", + "webUrl": "https://production-enterprise-screenshots.downloads.crowdin.com/992000002/6/2/middle.jpg?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIGJKLQV66ZXPMMEA%2F20190923%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20190923T093016Z&X-Amz-SignedHeaders=host&X-Amz-Expires=120&X-Amz-Signature=8df06f57594f7d1804b7c037629f6916224415e9b935c4f6619fbe002fb25e73", + "name": "translate_with_siri.jpg", + "size": { + "width": 267, + "height": 176 + }, + "tagsCount": 1, + "tags": [ + { + "id": 98, + "screenshotId": 2, + "stringId": 2822, + "position": { + "x": 474, + "y": 147, + "width": 490, + "height": 99 + }, + "createdAt": "2019-09-23T09:35:31+00:00" + } + ], + "labels": [ + 1 + ], + "labelIds": [ + 1 + ], + "createdAt": "2019-09-23T09:29:19+00:00", + "updatedAt": "2019-09-23T09:29:19+00:00" + } + } + ], + "pagination": { + "offset": 0, + "limit": 25 + } +} + \ No newline at end of file diff --git a/tests/Crowdin.Api.Tests/Screenshots/ScreenshotsApiTests.cs b/tests/Crowdin.Api.Tests/Screenshots/ScreenshotsApiTests.cs index 63c974f3..12a0c6ce 100644 --- a/tests/Crowdin.Api.Tests/Screenshots/ScreenshotsApiTests.cs +++ b/tests/Crowdin.Api.Tests/Screenshots/ScreenshotsApiTests.cs @@ -1,4 +1,5 @@ - + +using System.Collections.Generic; using System.Net; using System.Threading.Tasks; @@ -111,7 +112,7 @@ public async Task GetScreenshot() .ReturnsAsync(new CrowdinApiResult { StatusCode = HttpStatusCode.OK, - JsonObject = JObject.Parse(Core.Resources.Screenshots.AddScreenshot_Response) + JsonObject = JObject.Parse(Core.Resources.Screenshots.GetScreenshot_Response) }); var executor = new ScreenshotsApiExecutor(mockClient.Object); @@ -120,6 +121,49 @@ public async Task GetScreenshot() Assert.Equal(2, response.LabelIds.Length); Assert.Contains(0, response.LabelIds); Assert.Contains(1, response.LabelIds); - } + } + + [Fact] + public async Task ListScreenshots() + { + const int projectId = 1; + + var url = $"/projects/{projectId}/screenshots"; + var queryParams = new Dictionary + { + { "limit", "25" }, + { "offset", "0" }, + { "orderBy", "createdAt desc,name asc" }, + { "stringIds", "1,2,3,2822" } + }; + + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); + + mockClient + .Setup(client => client.SendGetRequest(url, queryParams)) + .ReturnsAsync(new CrowdinApiResult + { + StatusCode = HttpStatusCode.OK, + JsonObject = JObject.Parse(Core.Resources.Screenshots.ListScreenshots_Response) + }); + + var executor = new ScreenshotsApiExecutor(mockClient.Object); + var sortingRules = new SortingRule[] { + new SortingRule() { Field = "createdAt", Order = SortingOrder.Descending }, + new SortingRule() { Field = "name", Order = SortingOrder.Ascending } + }; + var stringIds = new int[] { 1, 2, 3, 2822 }; + var response = await executor.ListScreenshots(projectId, 25, 0, sortingRules, stringIds); + + Assert.NotNull(response); + + Assert.Equal(25, response.Pagination?.Limit); + Assert.Equal(0, response.Pagination?.Offset); + + Assert.Single(response.Data); + + Assert.Single(response.Data[0].Tags); + Assert.Equal(2822, response.Data[0].Tags[0].StringId); + } } } \ No newline at end of file diff --git a/tests/Crowdin.Api.Tests/SourceStrings/SourceStringsApiTests.cs b/tests/Crowdin.Api.Tests/SourceStrings/SourceStringsApiTests.cs index 2c953dbd..dc23e147 100644 --- a/tests/Crowdin.Api.Tests/SourceStrings/SourceStringsApiTests.cs +++ b/tests/Crowdin.Api.Tests/SourceStrings/SourceStringsApiTests.cs @@ -19,7 +19,7 @@ namespace Crowdin.Api.Tests.SourceStrings public class SourceStringsApiTests { private static readonly JsonSerializerSettings DefaultSettings = TestUtils.CreateJsonSerializerOptions(); - + [Fact] public void ListStrings_QueryStringConstruction() { @@ -29,20 +29,20 @@ public void ListStrings_QueryStringConstruction() { Scope = StringScope.Context }; - + Assert.Equal(expectedQueryString, TestUtils.ToQueryString(@params.ToQueryParams())); } - + [Fact] public async Task UploadStringsStatus() { const int projectId = 1; const string uploadId = "50fb3506-4127-4ba8-8296-f97dc7e3e0c3"; - + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); - + var url = $"/projects/{projectId}/strings/uploads/{uploadId}"; - + mockClient .Setup(client => client.SendGetRequest(url, null)) .ReturnsAsync(new CrowdinApiResult @@ -50,18 +50,18 @@ public async Task UploadStringsStatus() StatusCode = HttpStatusCode.OK, JsonObject = JObject.Parse(Core.Resources.SourceStrings.CommonResponses_UploadStrings) }); - + var executor = new SourceStringsApiExecutor(mockClient.Object); StringUploadResponseModel response = await executor.UploadStringsStatus(projectId, uploadId); - + Assert_StringUploadResponseModel(response); } - + [Fact] public async Task UploadStrings() { const int projectId = 1; - + var request = new UploadStringsRequest { StorageId = 61, @@ -85,15 +85,15 @@ public async Task UploadStrings() }, UpdateOption = UpdateOption.KeepTranslationsAndApprovals }; - + string actualRequestJson = JsonConvert.SerializeObject(request, DefaultSettings); string expectedRequestJson = TestUtils.CompactJson(Core.Resources.SourceStrings.UploadStrings_Request); Assert.Equal(expectedRequestJson, actualRequestJson); - + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); - + var url = $"/projects/{projectId}/strings/upload"; - + mockClient .Setup(client => client.SendPostRequest(url, request, null)) .ReturnsAsync(new CrowdinApiResult @@ -101,31 +101,31 @@ public async Task UploadStrings() StatusCode = HttpStatusCode.OK, JsonObject = JObject.Parse(Core.Resources.SourceStrings.CommonResponses_UploadStrings) }); - + var executor = new SourceStringsApiExecutor(mockClient.Object); StringUploadResponseModel response = await executor.UploadStrings(projectId, request); - + Assert_StringUploadResponseModel(response); } - + private static void Assert_StringUploadResponseModel(StringUploadResponseModel? response) { Assert.NotNull(response); ArgumentNullException.ThrowIfNull(response); - + Assert.Equal("50fb3506-4127-4ba8-8296-f97dc7e3e0c3", response.Identifier); Assert.Equal(OperationStatus.Finished, response.Status); Assert.Equal(100, response.Progress); - + DateTimeOffset date = DateTimeOffset.Parse("2019-09-23T11:26:54+00:00"); Assert.Equal(date, response.CreatedAt); Assert.Equal(date, response.UpdatedAt); Assert.Equal(date, response.StartedAt); Assert.Equal(date, response.FinishedAt); - + StringUploadResponseModel.AttributesData? attributes = response.Attributes; Assert.NotNull(attributes); - + Assert.Equal(38, attributes.BranchId); Assert.Equal(38, attributes.StorageId); Assert.Equal("android", attributes.FileType); @@ -134,10 +134,10 @@ private static void Assert_StringUploadResponseModel(StringUploadResponseModel? Assert.False(attributes.UpdateStrings); Assert.False(attributes.CleanupMode); Assert.Equal(UpdateOption.KeepTranslationsAndApprovals, attributes.UpdateOption); - + SpreadsheetFileImportOptions? importOptions = attributes.ImportOptions; Assert.NotNull(importOptions); - + Assert.False(importOptions.FirstLineContainsHeader); Assert.True(importOptions.ImportTranslations); Assert.Equal(new Dictionary