Skip to content

Commit 1059967

Browse files
SDK Update: uri encoded query params
🌿 Fern Regeneration -- July 31, 2025
2 parents 5acc898 + 504b5ad commit 1059967

File tree

165 files changed

+11942
-409
lines changed

Some content is hidden

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

165 files changed

+11942
-409
lines changed

src/Merge.Client.Test/Core/Pagination/StringCursorTest.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public class StringCursorTest
1010
private const string? Cursor1 = null;
1111
private const string Cursor2 = "cursor2";
1212
private const string Cursor3 = "cursor3";
13+
private const string Cursor4 = "";
1314

1415
[Test]
1516
public async SystemTask CursorPagerShouldWorkWithStringCursor()
@@ -80,6 +81,69 @@ public async SystemTask CursorPagerShouldWorkWithStringCursor()
8081
Assert.That(await pageEnumerator.MoveNextAsync(), Is.False);
8182
}
8283

84+
[Test]
85+
public async SystemTask CursorPagerShouldWorkWithStringCursor_EmptyStringCursor()
86+
{
87+
var responses = new List<Response>
88+
{
89+
new()
90+
{
91+
Data = new Data { Items = ["item1", "item2"] },
92+
Cursor = new Cursor { Next = Cursor2 },
93+
},
94+
new()
95+
{
96+
Data = new Data { Items = ["item1"] },
97+
Cursor = new Cursor { Next = Cursor4 },
98+
},
99+
new()
100+
{
101+
Data = new Data { Items = ["item2"] },
102+
Cursor = new Cursor { Next = Cursor3 },
103+
},
104+
}.GetEnumerator();
105+
var cursorCopy = Cursor1;
106+
Pager<object> pager = await CursorPager<
107+
Request,
108+
object?,
109+
Response,
110+
string,
111+
object
112+
>.CreateInstanceAsync(
113+
new Request { Cursor = Cursor1 },
114+
null,
115+
(_, _, _) =>
116+
{
117+
responses.MoveNext();
118+
return SystemTask.FromResult(responses.Current);
119+
},
120+
(request, cursor) =>
121+
{
122+
request.Cursor = cursor;
123+
cursorCopy = cursor;
124+
},
125+
response => response?.Cursor?.Next,
126+
response => response?.Data?.Items?.ToList()
127+
);
128+
129+
var pageEnumerator = pager.AsPagesAsync().GetAsyncEnumerator();
130+
131+
// first page
132+
Assert.That(await pageEnumerator.MoveNextAsync(), Is.True);
133+
var page = pageEnumerator.Current;
134+
Assert.That(page.Items, Has.Count.EqualTo(2));
135+
Assert.That(cursorCopy, Is.EqualTo(Cursor2));
136+
137+
// second page (cursor is empty string)
138+
Assert.That(await pageEnumerator.MoveNextAsync(), Is.True);
139+
page = pageEnumerator.Current;
140+
Assert.That(page.Items, Has.Count.EqualTo(1));
141+
Assert.That(cursorCopy, Is.EqualTo(Cursor4));
142+
143+
// no more (should not reach third response)
144+
Assert.That(await pageEnumerator.MoveNextAsync(), Is.False);
145+
}
146+
83147
private class Request
84148
{
85149
public required string? Cursor { get; set; }
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using Merge.Client.Core;
2+
using NUnit.Framework;
3+
using WireMock.Matchers;
4+
using WireMock.Server;
5+
using SystemTask = global::System.Threading.Tasks.Task;
6+
using WireMockRequest = WireMock.RequestBuilders.Request;
7+
using WireMockResponse = WireMock.ResponseBuilders.Response;
8+
9+
namespace Merge.Client.Test.Core.RawClientTests;
10+
11+
[TestFixture]
12+
[Parallelizable(ParallelScope.Self)]
13+
public class QueryParameterTests
14+
{
15+
private WireMockServer _server;
16+
private HttpClient _httpClient;
17+
private RawClient _rawClient;
18+
private string _baseUrl;
19+
20+
[SetUp]
21+
public void SetUp()
22+
{
23+
_server = WireMockServer.Start();
24+
_baseUrl = _server.Url ?? "";
25+
_httpClient = new HttpClient { BaseAddress = new Uri(_baseUrl) };
26+
_rawClient = new RawClient(new ClientOptions { HttpClient = _httpClient });
27+
}
28+
29+
[Test]
30+
public void CreateRequest_QueryParametersEscaping()
31+
{
32+
_server
33+
.Given(WireMockRequest.Create().WithPath("/test").WithParam("foo", "bar").UsingGet())
34+
.RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success"));
35+
36+
var request = new JsonRequest()
37+
{
38+
BaseUrl = _baseUrl,
39+
Method = HttpMethod.Get,
40+
Path = "/test",
41+
Query = new Dictionary<string, object>
42+
{
43+
{ "sample", "value" },
44+
{ "email", "[email protected]" },
45+
{ "%Complete", "100" },
46+
},
47+
Options = new RequestOptions(),
48+
};
49+
50+
var url = _rawClient.CreateHttpRequest(request).RequestUri!.AbsoluteUri;
51+
52+
Assert.That(url, Does.Contain("sample=value"));
53+
Assert.That(url, Does.Contain("email=bob%2Btest%40example.com"));
54+
Assert.That(url, Does.Contain("%25Complete=100"));
55+
}
56+
57+
[TearDown]
58+
public void TearDown()
59+
{
60+
_server.Dispose();
61+
_httpClient.Dispose();
62+
}
63+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using global::System.Threading.Tasks;
2+
using Merge.Client.Accounting;
3+
using Merge.Client.Core;
4+
using Merge.Client.Test.Unit.MockServer;
5+
using NUnit.Framework;
6+
7+
namespace Merge.Client.Test.Unit.MockServer.Accounting;
8+
9+
[TestFixture]
10+
public class CreateTest : BaseMockServerTest
11+
{
12+
[Test]
13+
public async global::System.Threading.Tasks.Task MockServerTest()
14+
{
15+
const string requestJson = """
16+
{
17+
"event": "event",
18+
"is_active": true
19+
}
20+
""";
21+
22+
const string mockResponse = """
23+
{
24+
"event": "event",
25+
"is_active": true,
26+
"key": "key"
27+
}
28+
""";
29+
30+
Server
31+
.Given(
32+
WireMock
33+
.RequestBuilders.Request.Create()
34+
.WithPath("/accounting/v1/webhook-receivers")
35+
.WithHeader("Content-Type", "application/json")
36+
.UsingPost()
37+
.WithBodyAsJson(requestJson)
38+
)
39+
.RespondWith(
40+
WireMock
41+
.ResponseBuilders.Response.Create()
42+
.WithStatusCode(200)
43+
.WithBody(mockResponse)
44+
);
45+
46+
var response = await Client.Accounting.WebhookReceivers.CreateAsync(
47+
new WebhookReceiverRequest { Event = "event", IsActive = true }
48+
);
49+
Assert.That(
50+
response,
51+
Is.EqualTo(JsonUtils.Deserialize<WebhookReceiver>(mockResponse)).UsingDefaults()
52+
);
53+
}
54+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using global::System.Threading.Tasks;
2+
using Merge.Client.Accounting;
3+
using Merge.Client.Core;
4+
using Merge.Client.Test.Unit.MockServer;
5+
using NUnit.Framework;
6+
7+
namespace Merge.Client.Test.Unit.MockServer.Accounting;
8+
9+
[TestFixture]
10+
public class DefaultScopesRetrieveTest : BaseMockServerTest
11+
{
12+
[Test]
13+
public async global::System.Threading.Tasks.Task MockServerTest()
14+
{
15+
const string mockResponse = """
16+
{
17+
"common_models": [
18+
{
19+
"model_name": "Employee",
20+
"model_permissions": {
21+
"READ": {
22+
"is_enabled": true
23+
},
24+
"WRITE": {
25+
"is_enabled": false
26+
}
27+
},
28+
"field_permissions": {
29+
"enabled_fields": [
30+
"avatar",
31+
"created_at",
32+
"custom_fields",
33+
"date_of_birth",
34+
"first_name",
35+
"gender",
36+
"remote_created_at",
37+
"remote_data"
38+
],
39+
"disabled_fields": [
40+
"company",
41+
"employments",
42+
"groups",
43+
"home_location",
44+
"manager",
45+
"work_location"
46+
]
47+
}
48+
}
49+
]
50+
}
51+
""";
52+
53+
Server
54+
.Given(
55+
WireMock
56+
.RequestBuilders.Request.Create()
57+
.WithPath("/accounting/v1/default-scopes")
58+
.UsingGet()
59+
)
60+
.RespondWith(
61+
WireMock
62+
.ResponseBuilders.Response.Create()
63+
.WithStatusCode(200)
64+
.WithBody(mockResponse)
65+
);
66+
67+
var response = await Client.Accounting.Scopes.DefaultScopesRetrieveAsync();
68+
Assert.That(
69+
response,
70+
Is.EqualTo(JsonUtils.Deserialize<CommonModelScopeApi>(mockResponse)).UsingDefaults()
71+
);
72+
}
73+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Merge.Client.Test.Unit.MockServer;
2+
using NUnit.Framework;
3+
4+
namespace Merge.Client.Test.Unit.MockServer.Accounting;
5+
6+
[TestFixture]
7+
public class DeleteTest : BaseMockServerTest
8+
{
9+
[Test]
10+
public void MockServerTest()
11+
{
12+
Server
13+
.Given(
14+
WireMock
15+
.RequestBuilders.Request.Create()
16+
.WithPath("/accounting/v1/delete-account")
17+
.UsingPost()
18+
)
19+
.RespondWith(WireMock.ResponseBuilders.Response.Create().WithStatusCode(200));
20+
21+
Assert.DoesNotThrowAsync(async () => await Client.Accounting.DeleteAccount.DeleteAsync());
22+
}
23+
}

0 commit comments

Comments
 (0)