Skip to content

Commit 890a81b

Browse files
authored
test: add unit tests for Edit, Delete, and Find operations in FileMakerRestClient (#418)
1 parent ce07c1f commit 890a81b

5 files changed

Lines changed: 1039 additions & 0 deletions

File tree

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
using System;
2+
using System.Linq;
3+
using System.Net;
4+
using System.Net.Http;
5+
using System.Threading.Tasks;
6+
using FMData.Rest.Tests.TestModels;
7+
using RichardSzalay.MockHttp;
8+
using Xunit;
9+
10+
namespace FMData.Rest.Tests
11+
{
12+
public class EditDeleteGetByIdTests
13+
{
14+
private static readonly string Server = "http://localhost";
15+
private static readonly string File = "test-file";
16+
private static readonly string Layout = "Users";
17+
18+
private static ConnectionInfo TestConnection => new ConnectionInfo
19+
{
20+
FmsUri = Server,
21+
Database = File,
22+
Username = "unit",
23+
Password = "test"
24+
};
25+
26+
private static MockHttpMessageHandler CreateMock()
27+
{
28+
var mockHttp = new MockHttpMessageHandler();
29+
30+
mockHttp.When(HttpMethod.Post, $"{Server}/fmi/data/v1/databases/{File}/sessions")
31+
.Respond("application/json", DataApiResponses.SuccessfulAuthentication());
32+
33+
mockHttp.When(HttpMethod.Delete, $"{Server}/fmi/data/v1/databases/{File}/sessions*")
34+
.Respond(HttpStatusCode.OK, "application/json", "");
35+
36+
return mockHttp;
37+
}
38+
39+
#region EditAsync Overloads
40+
41+
[Fact(DisplayName = "EditAsync(int, T) passthrough should succeed")]
42+
public async Task EditAsync_RecordIdAndModel_ShouldSucceed()
43+
{
44+
var mockHttp = CreateMock();
45+
46+
mockHttp.When(new HttpMethod("PATCH"), $"{Server}/fmi/data/v1/databases/{File}/layouts/{Layout}/records/42")
47+
.Respond("application/json", DataApiResponses.SuccessfulEdit());
48+
49+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
50+
51+
var response = await client.EditAsync(42, new User { Name = "updated" });
52+
53+
Assert.NotNull(response);
54+
Assert.Contains(response.Messages, r => r.Message == "OK");
55+
}
56+
57+
[Fact(DisplayName = "EditAsync(int, T, bool, bool) includes null values when flag set")]
58+
public async Task EditAsync_WithNullValueFlag_ShouldSucceed()
59+
{
60+
var mockHttp = CreateMock();
61+
62+
mockHttp.When(new HttpMethod("PATCH"), $"{Server}/fmi/data/v1/databases/{File}/layouts/{Layout}/records/42")
63+
.Respond("application/json", DataApiResponses.SuccessfulEdit());
64+
65+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
66+
67+
var response = await client.EditAsync(42, new User { Name = "updated", ForeignKeyId = null },
68+
includeNullValues: true, includeDefaultValues: true);
69+
70+
Assert.NotNull(response);
71+
Assert.Contains(response.Messages, r => r.Message == "OK");
72+
}
73+
74+
#endregion
75+
76+
#region GetByFileMakerIdAsync Overloads
77+
78+
[Fact(DisplayName = "GetByFileMakerIdAsync(int) infers layout from type")]
79+
public async Task GetByFileMakerIdAsync_InfersLayout()
80+
{
81+
var mockHttp = CreateMock();
82+
83+
mockHttp.When(HttpMethod.Get, $"{Server}/fmi/data/v1/databases/{File}/layouts/{Layout}/records/4")
84+
.Respond("application/json", DataApiResponses.SuccessfulGetById(4));
85+
86+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
87+
88+
var result = await client.GetByFileMakerIdAsync<User>(4, fmId: null);
89+
90+
Assert.NotNull(result);
91+
Assert.Equal("fuzzzerd", result.Name);
92+
}
93+
94+
[Fact(DisplayName = "GetByFileMakerIdAsync with fmIdFunc maps RecordId")]
95+
public async Task GetByFileMakerIdAsync_WithFmIdFunc_MapsRecordId()
96+
{
97+
var mockHttp = CreateMock();
98+
99+
mockHttp.When(HttpMethod.Get, $"{Server}/fmi/data/v1/databases/{File}/layouts/{Layout}/records/4")
100+
.Respond("application/json", DataApiResponses.SuccessfulGetById(4));
101+
102+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
103+
104+
var result = await client.GetByFileMakerIdAsync<User>(4,
105+
fmId: (o, id) => o.FileMakerRecordId = id);
106+
107+
Assert.NotNull(result);
108+
Assert.Equal(4, result.FileMakerRecordId);
109+
}
110+
111+
[Fact(DisplayName = "GetByFileMakerIdAsync with both mappers maps RecordId and ModId")]
112+
public async Task GetByFileMakerIdAsync_WithBothMappers_MapsBothIds()
113+
{
114+
var mockHttp = CreateMock();
115+
116+
mockHttp.When(HttpMethod.Get, $"{Server}/fmi/data/v1/databases/{File}/layouts/{Layout}/records/4")
117+
.Respond("application/json", DataApiResponses.SuccessfulGetById(4));
118+
119+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
120+
121+
var result = await client.GetByFileMakerIdAsync<User>(4,
122+
fmId: (o, id) => o.FileMakerRecordId = id,
123+
fmMod: (o, modId) => o.FileMakerModId = modId);
124+
125+
Assert.NotNull(result);
126+
Assert.Equal(4, result.FileMakerRecordId);
127+
Assert.Equal(0, result.FileMakerModId);
128+
}
129+
130+
[Fact(DisplayName = "GetByFileMakerIdAsync with explicit layout works")]
131+
public async Task GetByFileMakerIdAsync_WithExplicitLayout()
132+
{
133+
var mockHttp = CreateMock();
134+
135+
mockHttp.When(HttpMethod.Get, $"{Server}/fmi/data/v1/databases/{File}/layouts/CustomLayout/records/7")
136+
.Respond("application/json", DataApiResponses.SuccessfulGetById(7));
137+
138+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
139+
140+
var result = await client.GetByFileMakerIdAsync<User>("CustomLayout", 7,
141+
fmId: (o, id) => o.FileMakerRecordId = id);
142+
143+
Assert.NotNull(result);
144+
Assert.Equal(7, result.FileMakerRecordId);
145+
}
146+
147+
#endregion
148+
149+
#region ProcessContainers (batch)
150+
151+
[Fact(DisplayName = "ProcessContainers processes multiple models")]
152+
public async Task ProcessContainers_ProcessesMultipleModels()
153+
{
154+
var mockHttp = CreateMock();
155+
156+
// container URLs need to be valid URIs
157+
var url1 = $"{Server}/container1.jpg";
158+
var url2 = $"{Server}/container2.jpg";
159+
160+
mockHttp.When(HttpMethod.Get, url1)
161+
.Respond("application/octet-stream", new System.IO.MemoryStream(new byte[] { 1, 2, 3 }));
162+
163+
mockHttp.When(HttpMethod.Get, url2)
164+
.Respond("application/octet-stream", new System.IO.MemoryStream(new byte[] { 4, 5, 6 }));
165+
166+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
167+
168+
var models = new[]
169+
{
170+
new ContainerFieldTestModel { SomeContainerField = url1 },
171+
new ContainerFieldTestModel { SomeContainerField = url2 }
172+
};
173+
174+
await client.ProcessContainers(models);
175+
176+
Assert.NotNull(models[0].SomeContainerFieldData);
177+
Assert.NotNull(models[1].SomeContainerFieldData);
178+
}
179+
180+
#endregion
181+
182+
#region Obsolete Metadata Methods with Database Validation
183+
184+
[Fact(DisplayName = "GetLayoutsAsync(database) throws when database doesn't match")]
185+
public async Task GetLayoutsAsync_WithWrongDatabase_Throws()
186+
{
187+
var mockHttp = CreateMock();
188+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
189+
190+
#pragma warning disable CS0618 // intentionally testing the obsolete overload
191+
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(
192+
() => client.GetLayoutsAsync("wrong-database"));
193+
#pragma warning restore CS0618
194+
}
195+
196+
[Fact(DisplayName = "GetScriptsAsync(database) throws when database doesn't match")]
197+
public async Task GetScriptsAsync_WithWrongDatabase_Throws()
198+
{
199+
var mockHttp = CreateMock();
200+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
201+
202+
#pragma warning disable CS0618 // intentionally testing the obsolete overload
203+
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(
204+
() => client.GetScriptsAsync("wrong-database"));
205+
#pragma warning restore CS0618
206+
}
207+
208+
[Fact(DisplayName = "GetLayoutAsync(database, layout) throws when database doesn't match")]
209+
public async Task GetLayoutAsync_WithWrongDatabase_Throws()
210+
{
211+
var mockHttp = CreateMock();
212+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
213+
214+
#pragma warning disable CS0618 // intentionally testing the obsolete overload
215+
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(
216+
() => client.GetLayoutAsync("wrong-database", "layout"));
217+
#pragma warning restore CS0618
218+
}
219+
220+
#endregion
221+
}
222+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
using System.Net;
2+
using System.Net.Http;
3+
using System.Threading.Tasks;
4+
using FMData.Rest.Requests;
5+
using RichardSzalay.MockHttp;
6+
using Xunit;
7+
8+
namespace FMData.Rest.Tests
9+
{
10+
public class ExecuteRequestAsyncTests
11+
{
12+
private static readonly string Server = "http://localhost";
13+
private static readonly string File = "test-file";
14+
private static readonly string Layout = "layout";
15+
16+
private static ConnectionInfo TestConnection => new ConnectionInfo
17+
{
18+
FmsUri = Server,
19+
Database = File,
20+
Username = "unit",
21+
Password = "test"
22+
};
23+
24+
private static MockHttpMessageHandler CreateMock()
25+
{
26+
var mockHttp = new MockHttpMessageHandler();
27+
28+
mockHttp.When(HttpMethod.Post, $"{Server}/fmi/data/v1/databases/{File}/sessions")
29+
.Respond("application/json", DataApiResponses.SuccessfulAuthentication());
30+
31+
mockHttp.When(HttpMethod.Delete, $"{Server}/fmi/data/v1/databases/{File}/sessions*")
32+
.Respond(HttpStatusCode.OK, "application/json", "");
33+
34+
return mockHttp;
35+
}
36+
37+
#region GetRecordsEndpoint
38+
39+
[Fact(DisplayName = "GetRecordsEndpoint produces correct URL")]
40+
public void GetRecordsEndpoint_ProducesCorrectUrl()
41+
{
42+
var mockHttp = new MockHttpMessageHandler();
43+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
44+
45+
var endpoint = client.GetRecordsEndpoint(Layout, 10, 5);
46+
47+
Assert.Equal($"{Server}/fmi/data/v1/databases/{File}/layouts/{Layout}/records?_limit=10&_offset=5", endpoint);
48+
}
49+
50+
[Fact(DisplayName = "GetRecordsEndpoint escapes layout name")]
51+
public void GetRecordsEndpoint_EscapesLayoutName()
52+
{
53+
var mockHttp = new MockHttpMessageHandler();
54+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
55+
56+
var endpoint = client.GetRecordsEndpoint("My Layout", 10, 0);
57+
58+
Assert.Contains("My%20Layout", endpoint);
59+
}
60+
61+
#endregion
62+
63+
#region ExecuteRequestAsync Typed Overloads
64+
65+
[Fact(DisplayName = "ExecuteRequestAsync with CreateRequest sends POST")]
66+
public async Task ExecuteRequestAsync_CreateRequest_SendsPost()
67+
{
68+
var mockHttp = CreateMock();
69+
70+
mockHttp.When(HttpMethod.Post, $"{Server}/fmi/data/v1/databases/{File}/layouts/{Layout}/records")
71+
.Respond("application/json", DataApiResponses.SuccessfulCreate());
72+
73+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
74+
75+
var req = new CreateRequest<TestModels.User>
76+
{
77+
Layout = Layout,
78+
Data = new TestModels.User { Name = "test" }
79+
};
80+
81+
var response = await client.ExecuteRequestAsync(req);
82+
83+
Assert.True(response.IsSuccessStatusCode);
84+
}
85+
86+
[Fact(DisplayName = "ExecuteRequestAsync with FindRequest sends POST to _find")]
87+
public async Task ExecuteRequestAsync_FindRequest_SendsPostToFind()
88+
{
89+
var mockHttp = CreateMock();
90+
91+
mockHttp.When(HttpMethod.Post, $"{Server}/fmi/data/v1/databases/{File}/layouts/{Layout}/_find")
92+
.Respond("application/json", DataApiResponses.SuccessfulFind());
93+
94+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
95+
96+
var req = new FindRequest<TestModels.User> { Layout = Layout };
97+
req.AddQuery(new TestModels.User { Name = "test" }, false);
98+
99+
var response = await client.ExecuteRequestAsync(req);
100+
101+
Assert.True(response.IsSuccessStatusCode);
102+
}
103+
104+
[Fact(DisplayName = "ExecuteRequestAsync with EditRequest sends PATCH")]
105+
public async Task ExecuteRequestAsync_EditRequest_SendsPatch()
106+
{
107+
var mockHttp = CreateMock();
108+
109+
mockHttp.When(new HttpMethod("PATCH"), $"{Server}/fmi/data/v1/databases/{File}/layouts/{Layout}/records/42")
110+
.Respond("application/json", DataApiResponses.SuccessfulEdit());
111+
112+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
113+
114+
var req = new EditRequest<TestModels.User>
115+
{
116+
Layout = Layout,
117+
RecordId = 42,
118+
Data = new TestModels.User { Name = "updated" }
119+
};
120+
121+
var response = await client.ExecuteRequestAsync(req);
122+
123+
Assert.True(response.IsSuccessStatusCode);
124+
}
125+
126+
[Fact(DisplayName = "ExecuteRequestAsync with DeleteRequest sends DELETE")]
127+
public async Task ExecuteRequestAsync_DeleteRequest_SendsDelete()
128+
{
129+
var mockHttp = CreateMock();
130+
131+
mockHttp.When(HttpMethod.Delete, $"{Server}/fmi/data/v1/databases/{File}/layouts/{Layout}/records/99")
132+
.Respond("application/json", DataApiResponses.SuccessfulDelete());
133+
134+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
135+
136+
var req = new DeleteRequest { Layout = Layout, RecordId = 99 };
137+
138+
var response = await client.ExecuteRequestAsync(req);
139+
140+
Assert.True(response.IsSuccessStatusCode);
141+
}
142+
143+
#endregion
144+
145+
#region ExecuteRequestAsync Core Method
146+
147+
[Fact(DisplayName = "ExecuteRequestAsync refreshes token before sending")]
148+
public async Task ExecuteRequestAsync_RefreshesTokenBeforeSending()
149+
{
150+
var mockHttp = CreateMock();
151+
152+
mockHttp.When(HttpMethod.Post, $"{Server}/fmi/data/v1/databases/{File}/layouts/{Layout}/_find")
153+
.Respond("application/json", DataApiResponses.SuccessfulFind());
154+
155+
using var client = new FileMakerRestClient(mockHttp.ToHttpClient(), TestConnection);
156+
157+
Assert.False(client.IsAuthenticated);
158+
159+
var req = new FindRequest<TestModels.User> { Layout = Layout };
160+
req.AddQuery(new TestModels.User { Name = "test" }, false);
161+
162+
var response = await client.ExecuteRequestAsync(req);
163+
164+
Assert.True(client.IsAuthenticated);
165+
Assert.True(response.IsSuccessStatusCode);
166+
}
167+
168+
#endregion
169+
}
170+
}

0 commit comments

Comments
 (0)