Skip to content

Commit

Permalink
Merge pull request #11 from ducksoop/develop
Browse files Browse the repository at this point in the history
[ADD/UPDATE] Insert, Update, and Delete Commands
  • Loading branch information
hazeliscoding authored Feb 16, 2024
2 parents ea5323d + 23d2f04 commit dbbfb3e
Show file tree
Hide file tree
Showing 27 changed files with 2,695 additions and 139 deletions.
53 changes: 0 additions & 53 deletions QuickbaseNet.Examples/Program.cs

This file was deleted.

14 changes: 0 additions & 14 deletions QuickbaseNet.Examples/QuickbaseNet.Examples.csproj

This file was deleted.

1 change: 1 addition & 0 deletions QuickbaseNet.UnitTests/QuickbaseNet.UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoBogus" Version="2.13.1" />
<PackageReference Include="Bogus" Version="35.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="Moq" Version="4.20.70" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
using QuickbaseNet.Responses;
using QuickbaseNet.Services;
using QuickbaseNet.UnitTests.Mocks;
using QuickbaseNet.UnitTests.Utility;

namespace QuickbaseNet.UnitTests.QuickbaseClientTests;
namespace QuickbaseNet.UnitTests.Tests;

public class QuickbaseClientTests
{
Expand All @@ -22,11 +23,37 @@ public QuickbaseClientTests()
_client = CreateConfiguredQuickbaseClient();
}

[Fact]
public async Task Constructor_ThrowsArgumentNullException_WhenRealmIsNull()
{
// Arrange
var realm = string.Empty;

// Act
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() => Task.FromResult(new QuickbaseClient(realm, TestToken)));

// Assert
Assert.Equal("realm", exception.ParamName);
}

[Fact]
public async Task Constructor_ThrowsArgumentNullException_WhenTokenIsNull()
{
// Arrange
var token = string.Empty;

// Act
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() => Task.FromResult(new QuickbaseClient(TestRealm, token)));

// Assert
Assert.Equal("userToken", exception.ParamName);
}

[Fact]
public async Task QueryRecords_ReturnsSuccessResponse_WhenCalled()
{
// Arrange
var request = new QuickbaseQueryRequest();
var request = new Builder().Build<QuickbaseQueryRequest>();

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

[Fact]
public async Task QueryRecords_ReturnsErrorResponse_WhenBadRequestOccurs()
public async Task QueryRecords_ReturnsErrorResponse_When4xxOccurs()
{
// Arrange
SetupMockHandlerWithErrorResponse();
Expand All @@ -54,6 +81,27 @@ public async Task QueryRecords_ReturnsErrorResponse_WhenBadRequestOccurs()
// Act
var actualResponse = await _client.QueryRecords(request);

// Assert
Assert.False(actualResponse.IsSuccess);
Assert.True(actualResponse.IsFailure);
Assert.NotNull(actualResponse.QuickbaseError);
}

[Fact]
public async Task QueryRecords_ReturnsErrorResponse_When5xxOccurs()
{
// Arrange
_mockHandler.ResponseStatusCode = HttpStatusCode.InternalServerError;
var request = new QuickbaseQueryRequest
{
From = "tableId",
Where = "{1.CT.'query'}",
Select = [1, 2, 3]
};

// Act
var actualResponse = await _client.QueryRecords(request);

// Assert
Assert.False(actualResponse.IsSuccess);
Assert.NotNull(actualResponse.QuickbaseError);
Expand Down
37 changes: 37 additions & 0 deletions QuickbaseNet.UnitTests/Tests/QuickbaseResultTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using QuickbaseNet.Errors;

namespace QuickbaseNet.UnitTests.Tests;

public class QuickbaseResultTests
{
[Fact]
public void Constructor_ThrowsArgumentException_WhenInvalidErrorForSuccess()
{
// Arrange
var isSuccess = true;
var invalidError = QuickbaseError.ClientError("InvalidError", "Invalid error occurred", "Description");

// Act & Assert
Assert.Throws<ArgumentException>(() => new TestableQuickbaseResult(isSuccess, invalidError));
}

[Fact]
public void Constructor_ThrowsArgumentException_WhenInvalidErrorForFailure()
{
// Arrange
var isSuccess = false;
var invalidError = QuickbaseError.None;

// Act & Assert
Assert.Throws<ArgumentException>(() => new TestableQuickbaseResult(isSuccess, invalidError));
}
}

internal class TestableQuickbaseResult : QuickbaseResult
{
public TestableQuickbaseResult(bool isSuccess, QuickbaseError quickbaseError)
: base(isSuccess, quickbaseError)
{
// This constructor allows access to the protected internal constructor of QuickbaseResult
}
}
130 changes: 130 additions & 0 deletions QuickbaseNet.UnitTests/Utility/Builder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
namespace QuickbaseNet.UnitTests.Utility;

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;

using AutoBogus;

using Bogus;

[ExcludeFromCodeCoverage]
public class Builder
{
public T Build<T>() where T : class
{
var binder = new AutoBinder();

Faker<T> model = new AutoFaker<T>(binder)
.RuleForType(typeof(uint), rule => rule.Random.UInt(1, int.MaxValue))
.RuleForType(typeof(uint?), rule => (uint?)rule.Random.UInt(1, int.MaxValue))
.RuleForType(typeof(int), rule => rule.Random.Int())
.RuleForType(typeof(int?), rule => (int?)rule.Random.Int())
.RuleForType(typeof(DateTime), rule =>
{
DateTime date = rule.Date.Recent();
return new DateTime(
date.Year,
date.Month,
date.Day
);
})
.RuleForType(typeof(DateTime?), rule =>
{
DateTime date = rule.Date.Recent();
return (DateTime?)new DateTime(
date.Year,
date.Month,
date.Day
);
})
.RuleForType(typeof(DateTimeOffset?), rule =>
{
DateTimeOffset date = rule.Date.Recent();
return (DateTimeOffset?)new DateTime(
date.Year,
date.Month,
date.Day
);
})
.RuleForType(typeof(byte), rule => rule.Random.Byte())
.RuleForType(typeof(byte?), rule => (byte?)rule.Random.Byte())
.RuleForType(typeof(sbyte), rule => rule.Random.SByte())
.RuleForType(typeof(string), rule => rule.Random.AlphaNumeric(10))
.RuleForType(typeof(decimal), rule => rule.Finance.Amount(min: 0.01M, max: 99999.99M, decimals: 2))
.RuleForType(typeof(decimal?), rule => (decimal?)rule.Finance.Amount(min: 0.01M, max: 99999.99M, decimals: 2))
.RuleForType(typeof(ushort), rule => rule.Random.UShort())
.RuleForType(typeof(short), rule => rule.Random.Short())
.RuleForType(typeof(long), rule => rule.Random.Long())
.RuleForType(typeof(ulong), rule => rule.Random.ULong())
.RuleForType(typeof(bool), rule => rule.Random.Bool())
.RuleForType(typeof(bool?), rule => (bool?)rule.Random.Bool());

model = AddEnumRules(model, binder);

//int seed = DateTime.UtcNow.Millisecond;
//return model.UseSeed(seed).Generate();
return model.Generate();
}

/// <summary>
/// Adds the ability to generate random valid enum values
/// </summary>
/// <remarks>
/// 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
/// 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.
///
/// Bogus appears to do similar things when creating rules for well known types too, so this is at least similar.
///
/// 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.
/// This follows the convention for other nubmer types not generating zeroes
/// </remarks>
private Faker<T> AddEnumRules<T>(Faker<T> faker, IBinder binder) where T : class
{
// find enum properties using the binder
IEnumerable<PropertyInfo> enumProperties = binder.GetMembers(typeof(T))
.Select(item => item.Value)
.OfType<PropertyInfo>()
.Where(item => item.PropertyType.IsEnum);

Faker<T> result = faker;
if (enumProperties.Any())
{
result = result.FinishWith((fk, target) =>
{
foreach (PropertyInfo enumProperty in enumProperties)
{
// None = 0 is typically not a valid value, so for the sake of generating sane values it will be skipped
var minIndex = 0;
string[] enumNames = Enum.GetNames(enumProperty.PropertyType);
if (enumNames[0].ToLower() == "none")
{
minIndex = 1;
}

Array enumValues = Enum.GetValues(enumProperty.PropertyType);
var randomIndex = fk.Random.Int(minIndex, enumValues.Length - 1);
object randomEnumValue = enumValues.GetValue(randomIndex);

Check warning on line 109 in QuickbaseNet.UnitTests/Utility/Builder.cs

View workflow job for this annotation

GitHub Actions / build-and-deploy

Converting null literal or possible null value to non-nullable type.

Check warning on line 109 in QuickbaseNet.UnitTests/Utility/Builder.cs

View workflow job for this annotation

GitHub Actions / build-and-deploy

Converting null literal or possible null value to non-nullable type.

enumProperty.SetValue(target, randomEnumValue);
}
});
}

return result;
}

public IEnumerable<T> Build<T>(int howMany) where T : class
{
var models = new List<T>();
for (int i = 0; i < howMany; i++)
{
T model = Build<T>();
models.Add(model);
}

return models;
}
}
8 changes: 1 addition & 7 deletions QuickbaseNet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ VisualStudioVersion = 17.8.34322.80
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickbaseNet", "QuickbaseNet\QuickbaseNet.csproj", "{375B33E5-C837-4915-844C-52057055E84C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickbaseNet.Examples", "QuickbaseNet.Examples\QuickbaseNet.Examples.csproj", "{F92A2FF7-450E-4672-8781-BC648ACE2ACF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickbaseNet.UnitTests", "QuickbaseNet.UnitTests\QuickbaseNet.UnitTests.csproj", "{E2654CA5-971C-43D0-912E-D4445F1EB4B0}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickbaseNet.UnitTests", "QuickbaseNet.UnitTests\QuickbaseNet.UnitTests.csproj", "{E2654CA5-971C-43D0-912E-D4445F1EB4B0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -19,10 +17,6 @@ Global
{375B33E5-C837-4915-844C-52057055E84C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{375B33E5-C837-4915-844C-52057055E84C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{375B33E5-C837-4915-844C-52057055E84C}.Release|Any CPU.Build.0 = Release|Any CPU
{F92A2FF7-450E-4672-8781-BC648ACE2ACF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F92A2FF7-450E-4672-8781-BC648ACE2ACF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F92A2FF7-450E-4672-8781-BC648ACE2ACF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F92A2FF7-450E-4672-8781-BC648ACE2ACF}.Release|Any CPU.Build.0 = Release|Any CPU
{E2654CA5-971C-43D0-912E-D4445F1EB4B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E2654CA5-971C-43D0-912E-D4445F1EB4B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E2654CA5-971C-43D0-912E-D4445F1EB4B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down
Loading

0 comments on commit dbbfb3e

Please sign in to comment.