Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADD/UPDATE] Test Coverage, CI/CD, and General Enhancements #7

Merged
merged 5 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions QuickbaseNet.Examples/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ static async Task Main(string[] args)
var query = new QuickbaseQueryBuilder()
.From("bmycek2xq")
.Select(3, 7, 14, 75, 150, 157, 354, 355, 367, 538, 539, 540, 541, 542, 543)
.Where("{'7'.'CT'.'10136'}")
.Where("{'7'.'EX'.'10136'}")
.Build();

string jsonRequest = JsonConvert.SerializeObject(query);
Expand All @@ -22,12 +22,12 @@ static async Task Main(string[] args)
if (result.IsSuccess)
{
Console.WriteLine("Success!");
Console.WriteLine(JsonConvert.SerializeObject(result.Response, Formatting.Indented));
Console.WriteLine(JsonConvert.SerializeObject(result.Value, Formatting.Indented));
}
else
{
Console.WriteLine("Error!");
Console.WriteLine(JsonConvert.SerializeObject(result.Error, Formatting.Indented));
Console.WriteLine(JsonConvert.SerializeObject(result.QuickbaseError, Formatting.Indented));
}

// var recordBuilder = new QuickbaseCommandBuilder()
Expand Down
1 change: 1 addition & 0 deletions QuickbaseNet.UnitTests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
18 changes: 18 additions & 0 deletions QuickbaseNet.UnitTests/Mocks/MockHttpMessageHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Net;

namespace QuickbaseNet.UnitTests.Mocks;

public class MockHttpMessageHandler : HttpMessageHandler
{
public string ResponseContent { get; set; }
public HttpStatusCode ResponseStatusCode { get; set; }

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return await Task.FromResult(new HttpResponseMessage
{
StatusCode = ResponseStatusCode,
Content = new StringContent(ResponseContent)
});
}
}
124 changes: 124 additions & 0 deletions QuickbaseNet.UnitTests/QuickbaseClientTests/QuickbaseClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using System.Net;
using System.Text.Json;
using Bogus;
using QuickbaseNet.Requests;
using QuickbaseNet.Responses;
using QuickbaseNet.Services;
using QuickbaseNet.UnitTests.Mocks;

namespace QuickbaseNet.UnitTests.QuickbaseClientTests;

public class QuickbaseClientTests
{
private readonly MockHttpMessageHandler _mockHandler;
private readonly QuickbaseClient _client;
private const string TestRealm = "testRealm";
private const string TestToken = "testToken";
private const string BaseUrl = "http://localhost/";

public QuickbaseClientTests()
{
_mockHandler = SetupMockHandlerWithSuccessResponse();
_client = CreateConfiguredQuickbaseClient();
}

[Fact]
public async Task QueryRecords_ReturnsSuccessResponse_WhenCalled()
{
// Arrange
var request = new QuickbaseQueryRequest();

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

// Assert
Assert.True(response.IsSuccess);
Assert.NotNull(response.Value);
Assert.NotEmpty(response.Value.Data);
Assert.NotEmpty(response.Value.Fields);
Assert.NotNull(response.Value.Metadata);
}

[Fact]
public async Task QueryRecords_ReturnsErrorResponse_WhenBadRequestOccurs()
{
// Arrange
SetupMockHandlerWithErrorResponse();
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);
}

#region Helpers
private void SetupMockHandlerWithErrorResponse()
{
var faker = new Faker();
var errorResponse = new QuickbaseErrorResponse
{
Message = faker.Random.Words(),
Description = faker.Lorem.Paragraph()
};

_mockHandler.ResponseContent = JsonSerializer.Serialize(errorResponse);
_mockHandler.ResponseStatusCode = HttpStatusCode.BadRequest;
}

private MockHttpMessageHandler SetupMockHandlerWithSuccessResponse()
{
var mockJsonResponse = GetMockJsonResponse();
return new MockHttpMessageHandler
{
ResponseContent = mockJsonResponse,
ResponseStatusCode = HttpStatusCode.OK
};
}

private QuickbaseClient CreateConfiguredQuickbaseClient()
{
var httpClient = new HttpClient(_mockHandler)
{
BaseAddress = new Uri(BaseUrl)
};
return new QuickbaseClient(TestRealm, TestToken)
{
Client = httpClient
};
}

private static string GetMockJsonResponse()
{
// JSON response string...
return @"{
""data"": [
{
""6"": { ""value"": ""Andre Harris"" },
""7"": { ""value"": 10 },
""8"": { ""value"": ""2019-12-18T08:00:00Z"" }
}
],
""fields"": [
{ ""id"": 6, ""label"": ""Full Name"", ""type"": ""text"" },
{ ""id"": 7, ""label"": ""Amount"", ""type"": ""numeric"" },
{ ""id"": 8, ""label"": ""Date time"", ""type"": ""date time"" }
],
""metadata"": {
""totalRecords"": 10,
""numRecords"": 1,
""numFields"": 3,
""skip"": 0
}
}";
}

#endregion
}
35 changes: 35 additions & 0 deletions QuickbaseNet.UnitTests/QuickbaseNet.UnitTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Bogus" Version="35.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\QuickbaseNet\QuickbaseNet.csproj" />
</ItemGroup>

<ItemGroup>
<Folder Include="Mocks\" />
</ItemGroup>

</Project>
10 changes: 8 additions & 2 deletions QuickbaseNet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34322.80
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickbaseNet", "QuickbaseNet\QuickbaseNet.csproj", "{375B33E5-C837-4915-844C-52057055E84C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickbaseNet", "QuickbaseNet\QuickbaseNet.csproj", "{375B33E5-C837-4915-844C-52057055E84C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickbaseNet.Examples", "QuickbaseNet.Examples\QuickbaseNet.Examples.csproj", "{F92A2FF7-450E-4672-8781-BC648ACE2ACF}"
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}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -21,6 +23,10 @@ Global
{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
{E2654CA5-971C-43D0-912E-D4445F1EB4B0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
36 changes: 36 additions & 0 deletions QuickbaseNet/Errors/QuickbaseError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace QuickbaseNet.Errors
{
public class QuickbaseError
{
public static readonly QuickbaseError None = new QuickbaseError(string.Empty, string.Empty, string.Empty, QuickbaseErrorType.Failure);
public static readonly QuickbaseError NullValue = new QuickbaseError("Error.NullValue", "Null value was provided", string.Empty, QuickbaseErrorType.Failure);

public QuickbaseError(string code, string message, string description, QuickbaseErrorType quickbaseErrorType)
{
Code = code;
Message = message;
Description = description;
Type = quickbaseErrorType;
}

public string Code { get; private set; }

public string Message { get; private set; }

public string Description { get; private set; }

public QuickbaseErrorType Type { get; private set; }

public static QuickbaseError NotFound(string code, string message, string description) =>
new QuickbaseError(code, message, description, QuickbaseErrorType.NotFound);

public static QuickbaseError Failure(string code, string message, string description) =>
new QuickbaseError(code, message, description, QuickbaseErrorType.Failure);

public static QuickbaseError ClientError(string code, string message, string description) =>
new QuickbaseError(code, message, description, QuickbaseErrorType.ClientError);

public static QuickbaseError ServerError(string code, string message, string description) =>
new QuickbaseError(code, message, description, QuickbaseErrorType.ServerError);
}
}
10 changes: 10 additions & 0 deletions QuickbaseNet/Errors/QuickbaseErrorType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace QuickbaseNet.Errors
{
public enum QuickbaseErrorType
{
Failure = 0,
NotFound = 1,
ClientError = 3,
ServerError = 4
}
}
10 changes: 8 additions & 2 deletions QuickbaseNet/Models/FieldValue.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
using Newtonsoft.Json;
using System;

namespace QuickbaseNet.Models
{
public class FieldValue
{
[JsonProperty("value")]
public dynamic Value { get; set; }
[JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)]
public object Value { get; set; }

public T GetValue<T>()
{
return Value == null ? default : (T)Convert.ChangeType(Value, typeof(T));
}
}
}
1 change: 1 addition & 0 deletions QuickbaseNet/QuickbaseNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net5.0;net6.0;net48</TargetFrameworks>
<IsPublishable>True</IsPublishable>
<InternalsVisibleTo>QuickbaseNet.UnitTests</InternalsVisibleTo>
</PropertyGroup>

<ItemGroup>
Expand Down
53 changes: 53 additions & 0 deletions QuickbaseNet/QuickbaseResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using QuickbaseNet.Errors;

namespace QuickbaseNet
{
public class QuickbaseResult
{
protected internal QuickbaseResult(bool isSuccess, QuickbaseError quickbaseError)
{
if (isSuccess && quickbaseError != QuickbaseError.None ||
!isSuccess && quickbaseError == QuickbaseError.None)
{
throw new ArgumentException("Invalid error", nameof(quickbaseError));
}

IsSuccess = isSuccess;
QuickbaseError = quickbaseError;
}

public bool IsSuccess { get; }

public bool IsFailure => !IsSuccess;

public QuickbaseError QuickbaseError { get; }

public static QuickbaseResult Success() => new QuickbaseResult(true, QuickbaseError.None);

public static QuickbaseResult<TValue> Success<TValue>(TValue value) => new QuickbaseResult<TValue>(value, true, QuickbaseError.None);

public static QuickbaseResult Failure(QuickbaseError quickbaseError) => new QuickbaseResult(false, quickbaseError);

public static QuickbaseResult<TValue> Failure<TValue>(QuickbaseError quickbaseError) => new QuickbaseResult<TValue>(default, false, quickbaseError);
}

public class QuickbaseResult<TValue> : QuickbaseResult
{
private readonly TValue _value;

protected internal QuickbaseResult(TValue value, bool isSuccess, QuickbaseError quickbaseError)
: base(isSuccess, quickbaseError)
{
_value = value;
}

public TValue Value => IsSuccess
? _value
: throw new InvalidOperationException("The value of a failure result can't be accessed.");

public static implicit operator QuickbaseResult<TValue>(TValue value) =>
!(value == null) ? Success(value) : Failure<TValue>(QuickbaseError.NullValue);
}

}
Loading
Loading