diff --git a/AccessGridTest/ConsoleServiceTests.cs b/AccessGridTest/ConsoleServiceTests.cs index 8f56aa2..cd792e8 100644 --- a/AccessGridTest/ConsoleServiceTests.cs +++ b/AccessGridTest/ConsoleServiceTests.cs @@ -41,19 +41,21 @@ public async Task ListPassTemplatePairsAsync_ReturnsPassTemplatePairs() // Same fixture as Ruby console_spec.rb #list_pass_template_pairs var json = """ { - "pass_template_pairs": [ + "card_template_pairs": [ { "id": "pair_1", + "ex_id": "pair_1", "name": "Employee Badge Pair", "created_at": "2025-01-01T00:00:00Z", - "ios_template": { "id": "tmpl_ios_1", "name": "iOS Badge", "platform": "apple" }, - "android_template": { "id": "tmpl_android_1", "name": "Android Badge", "platform": "android" } + "ios_template": { "id": "tmpl_ios_1", "ex_id": "tmpl_ios_1", "name": "iOS Badge", "platform": "apple" }, + "android_template": { "id": "tmpl_android_1", "ex_id": "tmpl_android_1", "name": "Android Badge", "platform": "android" } }, { "id": "pair_2", + "ex_id": "pair_2", "name": "Contractor Badge Pair", "created_at": "2025-01-02T00:00:00Z", - "ios_template": { "id": "tmpl_ios_2", "name": "iOS Contractor", "platform": "apple" }, + "ios_template": { "id": "tmpl_ios_2", "ex_id": "tmpl_ios_2", "name": "iOS Contractor", "platform": "apple" }, "android_template": null } ], @@ -73,9 +75,11 @@ public async Task ListPassTemplatePairsAsync_ReturnsPassTemplatePairs() var first = result.PassTemplatePairs[0]; Assert.That(first.Id, Is.EqualTo("pair_1")); + Assert.That(first.ExId, Is.EqualTo("pair_1")); Assert.That(first.Name, Is.EqualTo("Employee Badge Pair")); Assert.That(first.CreatedAt, Is.EqualTo(new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc))); Assert.That(first.IosTemplate.Id, Is.EqualTo("tmpl_ios_1")); + Assert.That(first.IosTemplate.ExId, Is.EqualTo("tmpl_ios_1")); Assert.That(first.IosTemplate.Platform, Is.EqualTo("apple")); Assert.That(first.AndroidTemplate.Id, Is.EqualTo("tmpl_android_1")); Assert.That(first.AndroidTemplate.Platform, Is.EqualTo("android")); @@ -96,7 +100,7 @@ public async Task ListPassTemplatePairsAsync_PassesPaginationParams() { var json = """ { - "pass_template_pairs": [], + "card_template_pairs": [], "pagination": { "current_page": 2, "per_page": 10, "total_pages": 3, "total_count": 25 } } """; @@ -106,6 +110,7 @@ public async Task ListPassTemplatePairsAsync_PassesPaginationParams() _mockHttpClient.Verify(x => x.SendAsync(It.Is(req => req.Method == HttpMethod.Get && + req.RequestUri!.ToString().Contains("/v1/console/card-template-pairs") && req.RequestUri!.ToString().Contains("page=2") && req.RequestUri!.ToString().Contains("per_page=10") )), Times.Once); @@ -119,7 +124,7 @@ public async Task ListPassTemplatePairsAsync_HandlesEmptyResponse() { var json = """ { - "pass_template_pairs": [], + "card_template_pairs": [], "pagination": { "current_page": 1, "per_page": 50, "total_pages": 0, "total_count": 0 } } """; @@ -136,7 +141,7 @@ public async Task ListPassTemplatePairsAsync_SetsAuthHeaders() { var json = """ { - "pass_template_pairs": [], + "card_template_pairs": [], "pagination": { "current_page": 1, "per_page": 50, "total_pages": 0, "total_count": 0 } } """; @@ -151,6 +156,41 @@ public async Task ListPassTemplatePairsAsync_SetsAuthHeaders() )), Times.Once); } + [Test] + public async Task CreatePassTemplatePairAsync_PostsAndReturnsPair() + { + var json = """ + { + "id": "pair_new", + "ex_id": "pair_new", + "name": "New Badge Pair", + "created_at": "2025-04-15T12:00:00Z", + "ios_template": { "id": "tmpl_ios", "ex_id": "tmpl_ios", "name": "iOS Badge", "platform": "apple" }, + "android_template": { "id": "tmpl_android", "ex_id": "tmpl_android", "name": "Android Badge", "platform": "android" } + } + """; + StubHttpResponse(json, HttpStatusCode.Created); + + var result = await _client.Console.CreatePassTemplatePairAsync(new CreatePassTemplatePairRequest + { + Name = "New Badge Pair", + AppleCardTemplateId = "tmpl_ios", + GoogleCardTemplateId = "tmpl_android" + }); + + Assert.That(result, Is.Not.Null); + Assert.That(result.Id, Is.EqualTo("pair_new")); + Assert.That(result.ExId, Is.EqualTo("pair_new")); + Assert.That(result.Name, Is.EqualTo("New Badge Pair")); + Assert.That(result.IosTemplate.Platform, Is.EqualTo("apple")); + Assert.That(result.AndroidTemplate.Platform, Is.EqualTo("android")); + + _mockHttpClient.Verify(x => x.SendAsync(It.Is(req => + req.Method == HttpMethod.Post && + req.RequestUri!.ToString().Contains("/v1/console/card-template-pairs") + )), Times.Once); + } + #region CreateTemplateAsync [Test] diff --git a/README.md b/README.md index 4188eac..22b7a71 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Official C# SDK for interacting with the AccessGrid API. ## Installation ``` -Install-Package accessgrid -Version 1.4.0 +Install-Package accessgrid -Version 1.5.0 ``` ## Authentication @@ -381,6 +381,32 @@ public async Task ListPassTemplatePairsAsync() } ``` +### Creating a Pass Template Pair + +```csharp +using AccessGrid; +using System; +using System.Threading.Tasks; + +public async Task CreatePassTemplatePairAsync() +{ + var accountId = Environment.GetEnvironmentVariable("ACCOUNT_ID"); + var secretKey = Environment.GetEnvironmentVariable("SECRET_KEY"); + + using var client = new AccessGridClient(accountId, secretKey); + + // Both templates must be published (status: ready) and use the same protocol. + var pair = await client.Console.CreatePassTemplatePairAsync(new CreatePassTemplatePairRequest + { + Name = "Employee Badge Pair", + AppleCardTemplateId = "0xapplet3mp14t3", + GoogleCardTemplateId = "0xgoogl3t3mp14t3" + }); + + Console.WriteLine($"Created pair: {pair.Name} ({pair.Id})"); +} +``` + ### Getting Ledger Items ```csharp @@ -1043,7 +1069,8 @@ public class AccessCardsApiTests | PUT /v1/console/card-templates/{id} | `Console.UpdateTemplateAsync()` | Y | | GET /v1/console/card-templates/{id} | `Console.ReadTemplateAsync()` | Y | | GET /v1/console/card-templates/{id}/logs | `Console.EventLogAsync()` | Y | -| GET /v1/console/pass-template-pairs | `Console.ListPassTemplatePairsAsync()` | Y | +| GET /v1/console/card-template-pairs | `Console.ListPassTemplatePairsAsync()` | Y | +| POST /v1/console/card-template-pairs | `Console.CreatePassTemplatePairAsync()` | Y | | POST /v1/console/card-templates/{id}/ios_preflight | `Console.IosPreflightAsync()` | Y | | GET /v1/console/ledger-items | `Console.GetLedgerItemsAsync()` | Y | | GET /v1/console/landing-pages | `Console.ListLandingPagesAsync()` | Y | diff --git a/example/Example.cs b/example/Example.cs index c0eeee2..ce19772 100644 --- a/example/Example.cs +++ b/example/Example.cs @@ -28,7 +28,7 @@ static async Task Main() Console.WriteLine("API Credentials:"); Console.WriteLine($" Account ID: {accountId}"); Console.WriteLine($" Secret Key: {secretKey.Substring(0, 3)}...{secretKey.Substring(secretKey.Length - 3)}"); - + // Example 1: List cards Console.WriteLine("\nListing access cards..."); var cards = await client.AccessCards.ListAsync(new ListKeysRequest diff --git a/src/AccessGrid.csproj b/src/AccessGrid.csproj index 361cac5..dcb0afe 100644 --- a/src/AccessGrid.csproj +++ b/src/AccessGrid.csproj @@ -4,7 +4,7 @@ netstandard2.0 8.0 accessgrid - 1.4.0 + 1.5.0 AccessGrid AccessGrid Official C# SDK for the AccessGrid API diff --git a/src/AccessGrid/AccessCardsService.cs b/src/AccessGrid/AccessCardsService.cs index f5221fa..32bd1ba 100644 --- a/src/AccessGrid/AccessCardsService.cs +++ b/src/AccessGrid/AccessCardsService.cs @@ -67,10 +67,10 @@ public async Task UpdateAsync(string cardId, UpdateCardRequest reque public async Task> ListAsync(ListKeysRequest request) { var queryParams = new Dictionary(); - + if (!string.IsNullOrEmpty(request.TemplateId)) queryParams.Add("template_id", request.TemplateId); - + if (!string.IsNullOrEmpty(request.State)) queryParams.Add("state", request.State); diff --git a/src/AccessGrid/AccessGridClient.cs b/src/AccessGrid/AccessGridClient.cs index 1afe9b8..8c4923e 100644 --- a/src/AccessGrid/AccessGridClient.cs +++ b/src/AccessGrid/AccessGridClient.cs @@ -43,7 +43,7 @@ public AccessGridClient(string accountId, string secretKey, IHttpClientWrapper h { if (string.IsNullOrEmpty(accountId)) throw new ArgumentException("Account ID is required", nameof(accountId)); - + if (string.IsNullOrEmpty(secretKey)) throw new ArgumentException("Secret Key is required", nameof(secretKey)); @@ -222,10 +222,10 @@ private async Task MakeRequestAsync(HttpMethod method, string endpoint, ob { finalQueryParams["sig_payload"] = payload; } - + // Generate signature string signature = GenerateSignature(payload); - + // For GET requests or POST requests with empty bodies that need the sig_payload parameter // Note: We've already added sig_payload for /v1/key-cards endpoint above if ((method == HttpMethod.Get || (method == HttpMethod.Post && data == null)) && !finalQueryParams.ContainsKey("sig_payload")) @@ -279,12 +279,12 @@ private async Task MakeRequestAsync(HttpMethod method, string endpoint, ob { try { - var errorData = !string.IsNullOrEmpty(responseContent) - ? JsonSerializer.Deserialize>(responseContent, _jsonOptions) + var errorData = !string.IsNullOrEmpty(responseContent) + ? JsonSerializer.Deserialize>(responseContent, _jsonOptions) : null; - - var errorMessage = responseContent; - + + var errorMessage = responseContent; + throw new AccessGridException($"API request failed: {errorMessage}"); } catch (JsonException) diff --git a/src/AccessGrid/ConsoleService.cs b/src/AccessGrid/ConsoleService.cs index 36643f9..e06a128 100644 --- a/src/AccessGrid/ConsoleService.cs +++ b/src/AccessGrid/ConsoleService.cs @@ -75,18 +75,18 @@ public async Task