Skip to content

Commit f7a0747

Browse files
committed
refactor(tests): move test helper types into TestTypes folder
1 parent 56c365f commit f7a0747

35 files changed

Lines changed: 466 additions & 198 deletions

test/Atc.Rest.Client.Tests/BinaryEndpointResponseTests.cs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -146,23 +146,4 @@ public void ErrorContent_IsNull_WhenSuccessful()
146146
sut.IsSuccess.Should().BeTrue();
147147
sut.ErrorContent.Should().BeNull();
148148
}
149-
150-
private sealed class TestableBinaryEndpointResponse : BinaryEndpointResponse
151-
{
152-
public TestableBinaryEndpointResponse(
153-
bool isSuccess,
154-
HttpStatusCode statusCode,
155-
byte[]? content,
156-
string? contentType,
157-
string? fileName,
158-
long? contentLength)
159-
: base(isSuccess, statusCode, content, contentType, fileName, contentLength)
160-
{
161-
}
162-
163-
public InvalidOperationException GetInvalidContentAccessException(
164-
HttpStatusCode expectedStatusCode,
165-
string propertyName)
166-
=> InvalidContentAccessException(expectedStatusCode, propertyName);
167-
}
168149
}

test/Atc.Rest.Client.Tests/Builder/MessageRequestBuilderFileContentTests.cs

Lines changed: 78 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#pragma warning disable IDE0230
12
namespace Atc.Rest.Client.Tests.Builder;
23

34
public sealed class MessageRequestBuilderFileContentTests
@@ -210,76 +211,101 @@ public void DuckTyping_ObjectWithoutOpenReadStream_FallsBackToJson()
210211
serializer.Received(1).Serialize(notAFile);
211212
}
212213

213-
private sealed class TestFileContent : IFileContent
214+
[Fact]
215+
public void DuckTyping_IBrowserFileShape_PassesLongMaxValueAsMaxAllowedSize()
214216
{
215-
private readonly byte[] data;
217+
// Arrange
218+
var sut = CreateSut();
219+
var browserFile = new CapturingBrowserFileLike("doc.pdf", "application/pdf", [1, 2, 3]);
216220

217-
public TestFileContent(
218-
string fileName,
219-
string? contentType,
220-
byte[] data)
221-
{
222-
FileName = fileName;
223-
ContentType = contentType;
224-
this.data = data;
225-
}
221+
// Act
222+
sut.WithBody(browserFile);
223+
sut.Build(HttpMethod.Post);
226224

227-
public string FileName { get; }
225+
// Assert — ReflectedFileContent must pass long.MaxValue, not the 512000 default
226+
browserFile.CapturedMaxAllowedSize.Should().Be(long.MaxValue);
227+
}
228228

229-
public string? ContentType { get; }
229+
[Fact]
230+
public void DuckTyping_IBrowserFileShape_PassesCancellationTokenNone()
231+
{
232+
// Arrange
233+
var sut = CreateSut();
234+
var browserFile = new CapturingBrowserFileLike("doc.pdf", "application/pdf", [1, 2, 3]);
230235

231-
public Stream OpenReadStream() => new MemoryStream(data);
236+
// Act
237+
sut.WithBody(browserFile);
238+
sut.Build(HttpMethod.Post);
239+
240+
// Assert
241+
browserFile.CapturedCancellationToken.Should().Be(CancellationToken.None);
232242
}
233243

234-
/// <summary>
235-
/// Mimics the shape of IFormFile: FileName property + parameterless OpenReadStream().
236-
/// </summary>
237-
[SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Mimics IFormFile.OpenReadStream() method shape for duck-typing test")]
238-
internal sealed class FormFileLike
244+
[Fact]
245+
public void WithBody_IFileContent_NonSeekableStream_ProducesMultipartContent()
239246
{
240-
private readonly byte[] data;
247+
// Arrange
248+
var sut = CreateSut();
249+
var fileContent = new NonSeekableFileContent("stream.bin", "application/octet-stream", [10, 20, 30]);
241250

242-
public FormFileLike(
243-
string fileName,
244-
string contentType,
245-
byte[] data)
246-
{
247-
FileName = fileName;
248-
ContentType = contentType;
249-
this.data = data;
250-
}
251+
// Act
252+
sut.WithBody(fileContent);
253+
var message = sut.Build(HttpMethod.Post);
251254

252-
public string FileName { get; }
255+
// Assert — CopyTo must work even when the stream does not support Length/Position
256+
message.Content.Should().BeOfType<MultipartFormDataContent>();
257+
}
258+
259+
[Fact]
260+
public async Task WithBody_IFileContent_NonSeekableStream_ContainsCorrectBytes()
261+
{
262+
// Arrange
263+
var sut = CreateSut();
264+
byte[] data = [10, 20, 30];
265+
var fileContent = new NonSeekableFileContent("stream.bin", "application/octet-stream", data);
253266

254-
public string ContentType { get; }
267+
// Act
268+
sut.WithBody(fileContent);
269+
var message = sut.Build(HttpMethod.Post);
255270

256-
public Stream OpenReadStream() => new MemoryStream(data);
271+
// Assert
272+
var multipart = (MultipartFormDataContent)message.Content!;
273+
var bytes = await multipart.First().ReadAsByteArrayAsync();
274+
bytes.Should().BeEquivalentTo(data);
257275
}
258276

259-
/// <summary>
260-
/// Mimics the shape of IBrowserFile: Name property + OpenReadStream(long, CancellationToken) with defaults.
261-
/// </summary>
262-
[SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Mimics IBrowserFile.OpenReadStream() method shape for duck-typing test")]
263-
internal sealed class BrowserFileLike
277+
[Fact]
278+
public async Task WithBody_IFileContent_EmptyStream_ProducesEmptyContent()
264279
{
265-
private readonly byte[] data;
280+
// Arrange
281+
var sut = CreateSut();
282+
var fileContent = new NonSeekableFileContent("empty.bin", null, []);
266283

267-
public BrowserFileLike(
268-
string name,
269-
string contentType,
270-
byte[] data)
271-
{
272-
Name = name;
273-
ContentType = contentType;
274-
this.data = data;
275-
}
284+
// Act
285+
sut.WithBody(fileContent);
286+
var message = sut.Build(HttpMethod.Post);
276287

277-
public string Name { get; }
288+
// Assert
289+
var multipart = (MultipartFormDataContent)message.Content!;
290+
var bytes = await multipart.First().ReadAsByteArrayAsync();
291+
bytes.Should().BeEmpty();
292+
}
293+
294+
[Fact]
295+
public async Task DuckTyping_NonSeekableBrowserFileLike_ContainsCorrectBytes()
296+
{
297+
// Arrange — end-to-end: duck-typing + long.MaxValue + non-seekable stream
298+
var sut = CreateSut();
299+
byte[] data = [99, 100, 101, 102];
300+
var browserFile = new NonSeekableBrowserFileLike("blob.dat", "application/octet-stream", data);
278301

279-
public string ContentType { get; }
302+
// Act
303+
sut.WithBody(browserFile);
304+
var message = sut.Build(HttpMethod.Post);
280305

281-
public Stream OpenReadStream(
282-
long maxAllowedSize = 512000,
283-
CancellationToken cancellationToken = default) => new MemoryStream(data);
306+
// Assert
307+
var multipart = (MultipartFormDataContent)message.Content!;
308+
var bytes = await multipart.First().ReadAsByteArrayAsync();
309+
bytes.Should().BeEquivalentTo(data);
284310
}
285311
}

test/Atc.Rest.Client.Tests/EndpointResponseTests.cs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -419,23 +419,4 @@ public void Content_ReturnsRawStringContent()
419419
// Assert
420420
sut.Content.Should().Be(expectedContent);
421421
}
422-
423-
private sealed class TestableEndpointResponse : EndpointResponse
424-
{
425-
public TestableEndpointResponse(
426-
bool isSuccess,
427-
HttpStatusCode statusCode,
428-
string content,
429-
object? contentObject,
430-
IReadOnlyDictionary<string, IEnumerable<string>> headers)
431-
: base(isSuccess, statusCode, content, contentObject, headers)
432-
{
433-
}
434-
435-
public InvalidOperationException GetInvalidContentAccessException<TExpected>(
436-
HttpStatusCode expectedStatusCode,
437-
string propertyName)
438-
where TExpected : class
439-
=> InvalidContentAccessException<TExpected>(expectedStatusCode, propertyName);
440-
}
441422
}

test/Atc.Rest.Client.Tests/GlobalUsings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@
88
global using Atc.Rest.Client.Builder;
99
global using Atc.Rest.Client.Options;
1010
global using Atc.Rest.Client.Serialization;
11+
global using Atc.Rest.Client.Tests.TestTypes;
1112
global using Microsoft.Extensions.DependencyInjection;

test/Atc.Rest.Client.Tests/Options/AtcRestClientOptionsTests.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,4 @@ public void DerivedClass_CanOverrideProperties()
6060
sut.BaseAddress.Should().Be(new Uri("https://override.example.com"));
6161
sut.Timeout.Should().Be(TimeSpan.FromMinutes(2));
6262
}
63-
64-
private sealed class DerivedOptions : AtcRestClientOptions
65-
{
66-
public override Uri? BaseAddress { get; set; } = new Uri("https://override.example.com");
67-
68-
public override TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(2);
69-
}
7063
}

test/Atc.Rest.Client.Tests/Options/ServiceCollectionExtensionsTests.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -366,9 +366,4 @@ public void AddAtcRestClient_WithHttpsAndHttpBaseAddresses_BothWork()
366366
factory.CreateClient("SecureClient").BaseAddress.Should().Be(httpsAddress);
367367
factory.CreateClient("InsecureClient").BaseAddress.Should().Be(httpAddress);
368368
}
369-
370-
[SuppressMessage("Major Code Smell", "S2094:Classes should not be empty", Justification = "Test helper type")]
371-
private sealed class TestOptions : AtcRestClientOptions
372-
{
373-
}
374369
}

test/Atc.Rest.Client.Tests/Serialization/DefaultJsonContractSerializerTests.cs

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
namespace Atc.Rest.Client.Tests.Serialization;
22

3-
[SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Test helper types")]
43
public sealed class DefaultJsonContractSerializerTests
54
{
65
private readonly DefaultJsonContractSerializer sut = new();
@@ -433,29 +432,4 @@ public void Deserialize_PreservesUnicodeCharacters()
433432
result.Should().NotBeNull();
434433
result!.Name.Should().Be("日本語テスト");
435434
}
436-
437-
public sealed record TestModel(string Name, int Value);
438-
439-
public sealed record StatusContainer(TestStatus Status);
440-
441-
public enum TestStatus
442-
{
443-
Inactive,
444-
Active,
445-
Pending,
446-
}
447-
448-
public sealed class CircularModel
449-
{
450-
public CircularModel? Self { get; set; }
451-
}
452-
453-
public sealed record DateTimeModel(DateTimeOffset Timestamp);
454-
455-
public sealed record NestedModel(TestModel? Parent, TestModel? Child);
456-
457-
[SuppressMessage("Major Code Smell", "S2094:Classes should not be empty", Justification = "Test helper type")]
458-
public sealed class EmptyModel
459-
{
460-
}
461435
}

test/Atc.Rest.Client.Tests/Serialization/JsonSerializerOptionsExtensionsTests.cs

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -139,34 +139,4 @@ public void WithoutConverter_SourceRemainsUnchanged()
139139
source.Converters.Should().HaveCount(1);
140140
source.Converters[0].Should().BeSameAs(converter);
141141
}
142-
143-
private sealed class CustomTestConverter : JsonConverter<string>
144-
{
145-
public override string? Read(
146-
ref Utf8JsonReader reader,
147-
Type typeToConvert,
148-
JsonSerializerOptions options)
149-
=> reader.GetString();
150-
151-
public override void Write(
152-
Utf8JsonWriter writer,
153-
string value,
154-
JsonSerializerOptions options)
155-
=> writer.WriteStringValue(value);
156-
}
157-
158-
private sealed class AnotherTestConverter : JsonConverter<int>
159-
{
160-
public override int Read(
161-
ref Utf8JsonReader reader,
162-
Type typeToConvert,
163-
JsonSerializerOptions options)
164-
=> reader.GetInt32();
165-
166-
public override void Write(
167-
Utf8JsonWriter writer,
168-
int value,
169-
JsonSerializerOptions options)
170-
=> writer.WriteNumberValue(value);
171-
}
172142
}

test/Atc.Rest.Client.Tests/StreamBinaryEndpointResponseTests.cs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -206,23 +206,4 @@ public void ErrorContent_IsNull_WhenSuccessful()
206206
sut.IsSuccess.Should().BeTrue();
207207
sut.ErrorContent.Should().BeNull();
208208
}
209-
210-
private sealed class TestableStreamBinaryEndpointResponse : StreamBinaryEndpointResponse
211-
{
212-
public TestableStreamBinaryEndpointResponse(
213-
bool isSuccess,
214-
HttpStatusCode statusCode,
215-
Stream? contentStream,
216-
string? contentType,
217-
string? fileName,
218-
long? contentLength)
219-
: base(isSuccess, statusCode, contentStream, contentType, fileName, contentLength)
220-
{
221-
}
222-
223-
public InvalidOperationException GetInvalidContentAccessException(
224-
HttpStatusCode expectedStatusCode,
225-
string propertyName)
226-
=> InvalidContentAccessException(expectedStatusCode, propertyName);
227-
}
228209
}

test/Atc.Rest.Client.Tests/StreamingEndpointResponseTests.cs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -161,22 +161,4 @@ public void InvalidContentAccessException_ContainsPropertyName()
161161
// Assert
162162
exception.Message.Should().Contain("OKContent");
163163
}
164-
165-
private sealed class TestableStreamingEndpointResponse<T> : StreamingEndpointResponse<T>
166-
{
167-
public TestableStreamingEndpointResponse(
168-
bool isSuccess,
169-
HttpStatusCode statusCode,
170-
IAsyncEnumerable<T?>? content,
171-
string? errorContent,
172-
HttpResponseMessage? httpResponse)
173-
: base(isSuccess, statusCode, content, errorContent, httpResponse)
174-
{
175-
}
176-
177-
public InvalidOperationException GetInvalidContentAccessException(
178-
HttpStatusCode expectedStatusCode,
179-
string propertyName)
180-
=> InvalidContentAccessException(expectedStatusCode, propertyName);
181-
}
182164
}

0 commit comments

Comments
 (0)