Skip to content

Commit dbbfb3e

Browse files
authoredFeb 16, 2024
Merge pull request #11 from ducksoop/develop
[ADD/UPDATE] Insert, Update, and Delete Commands
2 parents ea5323d + 23d2f04 commit dbbfb3e

27 files changed

+2695
-139
lines changed
 

‎QuickbaseNet.Examples/Program.cs

Lines changed: 0 additions & 53 deletions
This file was deleted.

‎QuickbaseNet.Examples/QuickbaseNet.Examples.csproj

Lines changed: 0 additions & 14 deletions
This file was deleted.

‎QuickbaseNet.UnitTests/QuickbaseNet.UnitTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
</PropertyGroup>
1111

1212
<ItemGroup>
13+
<PackageReference Include="AutoBogus" Version="2.13.1" />
1314
<PackageReference Include="Bogus" Version="35.4.0" />
1415
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
1516
<PackageReference Include="Moq" Version="4.20.70" />

‎QuickbaseNet.UnitTests/QuickbaseClientTests/QuickbaseClientTests.cs renamed to ‎QuickbaseNet.UnitTests/Tests/QuickbaseClientTests.cs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
using QuickbaseNet.Responses;
66
using QuickbaseNet.Services;
77
using QuickbaseNet.UnitTests.Mocks;
8+
using QuickbaseNet.UnitTests.Utility;
89

9-
namespace QuickbaseNet.UnitTests.QuickbaseClientTests;
10+
namespace QuickbaseNet.UnitTests.Tests;
1011

1112
public class QuickbaseClientTests
1213
{
@@ -22,11 +23,37 @@ public QuickbaseClientTests()
2223
_client = CreateConfiguredQuickbaseClient();
2324
}
2425

26+
[Fact]
27+
public async Task Constructor_ThrowsArgumentNullException_WhenRealmIsNull()
28+
{
29+
// Arrange
30+
var realm = string.Empty;
31+
32+
// Act
33+
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() => Task.FromResult(new QuickbaseClient(realm, TestToken)));
34+
35+
// Assert
36+
Assert.Equal("realm", exception.ParamName);
37+
}
38+
39+
[Fact]
40+
public async Task Constructor_ThrowsArgumentNullException_WhenTokenIsNull()
41+
{
42+
// Arrange
43+
var token = string.Empty;
44+
45+
// Act
46+
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() => Task.FromResult(new QuickbaseClient(TestRealm, token)));
47+
48+
// Assert
49+
Assert.Equal("userToken", exception.ParamName);
50+
}
51+
2552
[Fact]
2653
public async Task QueryRecords_ReturnsSuccessResponse_WhenCalled()
2754
{
2855
// Arrange
29-
var request = new QuickbaseQueryRequest();
56+
var request = new Builder().Build<QuickbaseQueryRequest>();
3057

3158
// Act
3259
var response = await _client.QueryRecords(request);
@@ -40,7 +67,7 @@ public async Task QueryRecords_ReturnsSuccessResponse_WhenCalled()
4067
}
4168

4269
[Fact]
43-
public async Task QueryRecords_ReturnsErrorResponse_WhenBadRequestOccurs()
70+
public async Task QueryRecords_ReturnsErrorResponse_When4xxOccurs()
4471
{
4572
// Arrange
4673
SetupMockHandlerWithErrorResponse();
@@ -54,6 +81,27 @@ public async Task QueryRecords_ReturnsErrorResponse_WhenBadRequestOccurs()
5481
// Act
5582
var actualResponse = await _client.QueryRecords(request);
5683

84+
// Assert
85+
Assert.False(actualResponse.IsSuccess);
86+
Assert.True(actualResponse.IsFailure);
87+
Assert.NotNull(actualResponse.QuickbaseError);
88+
}
89+
90+
[Fact]
91+
public async Task QueryRecords_ReturnsErrorResponse_When5xxOccurs()
92+
{
93+
// Arrange
94+
_mockHandler.ResponseStatusCode = HttpStatusCode.InternalServerError;
95+
var request = new QuickbaseQueryRequest
96+
{
97+
From = "tableId",
98+
Where = "{1.CT.'query'}",
99+
Select = [1, 2, 3]
100+
};
101+
102+
// Act
103+
var actualResponse = await _client.QueryRecords(request);
104+
57105
// Assert
58106
Assert.False(actualResponse.IsSuccess);
59107
Assert.NotNull(actualResponse.QuickbaseError);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using QuickbaseNet.Errors;
2+
3+
namespace QuickbaseNet.UnitTests.Tests;
4+
5+
public class QuickbaseResultTests
6+
{
7+
[Fact]
8+
public void Constructor_ThrowsArgumentException_WhenInvalidErrorForSuccess()
9+
{
10+
// Arrange
11+
var isSuccess = true;
12+
var invalidError = QuickbaseError.ClientError("InvalidError", "Invalid error occurred", "Description");
13+
14+
// Act & Assert
15+
Assert.Throws<ArgumentException>(() => new TestableQuickbaseResult(isSuccess, invalidError));
16+
}
17+
18+
[Fact]
19+
public void Constructor_ThrowsArgumentException_WhenInvalidErrorForFailure()
20+
{
21+
// Arrange
22+
var isSuccess = false;
23+
var invalidError = QuickbaseError.None;
24+
25+
// Act & Assert
26+
Assert.Throws<ArgumentException>(() => new TestableQuickbaseResult(isSuccess, invalidError));
27+
}
28+
}
29+
30+
internal class TestableQuickbaseResult : QuickbaseResult
31+
{
32+
public TestableQuickbaseResult(bool isSuccess, QuickbaseError quickbaseError)
33+
: base(isSuccess, quickbaseError)
34+
{
35+
// This constructor allows access to the protected internal constructor of QuickbaseResult
36+
}
37+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
namespace QuickbaseNet.UnitTests.Utility;
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Diagnostics.CodeAnalysis;
6+
using System.Linq;
7+
using System.Reflection;
8+
9+
using AutoBogus;
10+
11+
using Bogus;
12+
13+
[ExcludeFromCodeCoverage]
14+
public class Builder
15+
{
16+
public T Build<T>() where T : class
17+
{
18+
var binder = new AutoBinder();
19+
20+
Faker<T> model = new AutoFaker<T>(binder)
21+
.RuleForType(typeof(uint), rule => rule.Random.UInt(1, int.MaxValue))
22+
.RuleForType(typeof(uint?), rule => (uint?)rule.Random.UInt(1, int.MaxValue))
23+
.RuleForType(typeof(int), rule => rule.Random.Int())
24+
.RuleForType(typeof(int?), rule => (int?)rule.Random.Int())
25+
.RuleForType(typeof(DateTime), rule =>
26+
{
27+
DateTime date = rule.Date.Recent();
28+
return new DateTime(
29+
date.Year,
30+
date.Month,
31+
date.Day
32+
);
33+
})
34+
.RuleForType(typeof(DateTime?), rule =>
35+
{
36+
DateTime date = rule.Date.Recent();
37+
return (DateTime?)new DateTime(
38+
date.Year,
39+
date.Month,
40+
date.Day
41+
);
42+
})
43+
.RuleForType(typeof(DateTimeOffset?), rule =>
44+
{
45+
DateTimeOffset date = rule.Date.Recent();
46+
return (DateTimeOffset?)new DateTime(
47+
date.Year,
48+
date.Month,
49+
date.Day
50+
);
51+
})
52+
.RuleForType(typeof(byte), rule => rule.Random.Byte())
53+
.RuleForType(typeof(byte?), rule => (byte?)rule.Random.Byte())
54+
.RuleForType(typeof(sbyte), rule => rule.Random.SByte())
55+
.RuleForType(typeof(string), rule => rule.Random.AlphaNumeric(10))
56+
.RuleForType(typeof(decimal), rule => rule.Finance.Amount(min: 0.01M, max: 99999.99M, decimals: 2))
57+
.RuleForType(typeof(decimal?), rule => (decimal?)rule.Finance.Amount(min: 0.01M, max: 99999.99M, decimals: 2))
58+
.RuleForType(typeof(ushort), rule => rule.Random.UShort())
59+
.RuleForType(typeof(short), rule => rule.Random.Short())
60+
.RuleForType(typeof(long), rule => rule.Random.Long())
61+
.RuleForType(typeof(ulong), rule => rule.Random.ULong())
62+
.RuleForType(typeof(bool), rule => rule.Random.Bool())
63+
.RuleForType(typeof(bool?), rule => (bool?)rule.Random.Bool());
64+
65+
model = AddEnumRules(model, binder);
66+
67+
//int seed = DateTime.UtcNow.Millisecond;
68+
//return model.UseSeed(seed).Generate();
69+
return model.Generate();
70+
}
71+
72+
/// <summary>
73+
/// Adds the ability to generate random valid enum values
74+
/// </summary>
75+
/// <remarks>
76+
/// Bogus currently doesn't appear to have a way to create rules for types that aren't well known at compile time. To generate random values for enum properties
77+
/// we have to find properties on <typeparamref name="T"/> that are enum and get a list of valid values for that enum and pick one of those randomly.
78+
///
79+
/// Bogus appears to do similar things when creating rules for well known types too, so this is at least similar.
80+
///
81+
/// This also excludes enum members whose name is "None" when it is the first member of the enum as that is usually not a valid value.
82+
/// This follows the convention for other nubmer types not generating zeroes
83+
/// </remarks>
84+
private Faker<T> AddEnumRules<T>(Faker<T> faker, IBinder binder) where T : class
85+
{
86+
// find enum properties using the binder
87+
IEnumerable<PropertyInfo> enumProperties = binder.GetMembers(typeof(T))
88+
.Select(item => item.Value)
89+
.OfType<PropertyInfo>()
90+
.Where(item => item.PropertyType.IsEnum);
91+
92+
Faker<T> result = faker;
93+
if (enumProperties.Any())
94+
{
95+
result = result.FinishWith((fk, target) =>
96+
{
97+
foreach (PropertyInfo enumProperty in enumProperties)
98+
{
99+
// None = 0 is typically not a valid value, so for the sake of generating sane values it will be skipped
100+
var minIndex = 0;
101+
string[] enumNames = Enum.GetNames(enumProperty.PropertyType);
102+
if (enumNames[0].ToLower() == "none")
103+
{
104+
minIndex = 1;
105+
}
106+
107+
Array enumValues = Enum.GetValues(enumProperty.PropertyType);
108+
var randomIndex = fk.Random.Int(minIndex, enumValues.Length - 1);
109+
object randomEnumValue = enumValues.GetValue(randomIndex);
110+
111+
enumProperty.SetValue(target, randomEnumValue);
112+
}
113+
});
114+
}
115+
116+
return result;
117+
}
118+
119+
public IEnumerable<T> Build<T>(int howMany) where T : class
120+
{
121+
var models = new List<T>();
122+
for (int i = 0; i < howMany; i++)
123+
{
124+
T model = Build<T>();
125+
models.Add(model);
126+
}
127+
128+
return models;
129+
}
130+
}

‎QuickbaseNet.sln

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ VisualStudioVersion = 17.8.34322.80
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickbaseNet", "QuickbaseNet\QuickbaseNet.csproj", "{375B33E5-C837-4915-844C-52057055E84C}"
77
EndProject
8-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickbaseNet.Examples", "QuickbaseNet.Examples\QuickbaseNet.Examples.csproj", "{F92A2FF7-450E-4672-8781-BC648ACE2ACF}"
9-
EndProject
10-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickbaseNet.UnitTests", "QuickbaseNet.UnitTests\QuickbaseNet.UnitTests.csproj", "{E2654CA5-971C-43D0-912E-D4445F1EB4B0}"
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickbaseNet.UnitTests", "QuickbaseNet.UnitTests\QuickbaseNet.UnitTests.csproj", "{E2654CA5-971C-43D0-912E-D4445F1EB4B0}"
119
EndProject
1210
Global
1311
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -19,10 +17,6 @@ Global
1917
{375B33E5-C837-4915-844C-52057055E84C}.Debug|Any CPU.Build.0 = Debug|Any CPU
2018
{375B33E5-C837-4915-844C-52057055E84C}.Release|Any CPU.ActiveCfg = Release|Any CPU
2119
{375B33E5-C837-4915-844C-52057055E84C}.Release|Any CPU.Build.0 = Release|Any CPU
22-
{F92A2FF7-450E-4672-8781-BC648ACE2ACF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23-
{F92A2FF7-450E-4672-8781-BC648ACE2ACF}.Debug|Any CPU.Build.0 = Debug|Any CPU
24-
{F92A2FF7-450E-4672-8781-BC648ACE2ACF}.Release|Any CPU.ActiveCfg = Release|Any CPU
25-
{F92A2FF7-450E-4672-8781-BC648ACE2ACF}.Release|Any CPU.Build.0 = Release|Any CPU
2620
{E2654CA5-971C-43D0-912E-D4445F1EB4B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
2721
{E2654CA5-971C-43D0-912E-D4445F1EB4B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
2822
{E2654CA5-971C-43D0-912E-D4445F1EB4B0}.Release|Any CPU.ActiveCfg = Release|Any CPU

0 commit comments

Comments
 (0)