Skip to content
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
29 changes: 1 addition & 28 deletions .github/release-drafter.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,4 @@
name: Release Drafter

on:
push:
# branches to consider in the event; optional, defaults to all
branches:
- main

permissions:
contents: read

categories:

- title: '🚀 Enhancements'
labels:
- feature
Expand All @@ -24,7 +12,7 @@ categories:
- bugfix
- bug
- title: '🧰 Maintenance'
label:
labels:
- chore

template: |
Expand All @@ -42,18 +30,3 @@ template: |

exclude-labels:
- skip-changelog

jobs:
update_release_draft:
permissions:
# write permission is required to create a github release
contents: write
# write permission is required for autolabeler
# otherwise, read permission is required at least
pull-requests: write
runs-on: ubuntu-latest
steps:
# Drafts your next Release notes as Pull Requests are merged into "master"
- uses: release-drafter/release-drafter@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ jobs:

steps:
- uses: actions/checkout@v4
- name: Setup .NET 8.0
- name: Setup .NET 10.0
uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a
with:
dotnet-version: 8.0.x
dotnet-version: 10.0.x
- name: Install solution dependencies
run: dotnet restore
- name: Build
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup .NET 10.0
uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a
with:
dotnet-version: 10.0.x

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/release-drafter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Release Drafter

on:
push:
branches:
- main

permissions:
contents: read

jobs:
update_release_draft:
permissions:
contents: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>Unicorn.Approvals.ApprovalsService.Tests</AssemblyName>
<RootNamespace>Unicorn.Approvals.ApprovalsService.Tests</RootNamespace>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Conditions:

Globals:
Function:
Runtime: dotnet8
Runtime: dotnet10
MemorySize: 512
Timeout: 10
Tracing: Active
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
var mockDynamoDbClient = Substitute.ForPartsOf<AmazonDynamoDBClient>();
mockDynamoDbClient.PutItemAsync(Arg.Any<PutItemRequest>())
.Returns(new PutItemResponse());

var context = TestHelpers.NewLambdaContext();

// Act
Expand All @@ -66,7 +66,65 @@

// Assert
await mockDynamoDbClient.Received(1).PutItemAsync(Arg.Any<PutItemRequest>());


}

[Fact]
public async Task Create_contract_with_snake_case_json_populates_all_dynamodb_attributes()
{
// Arrange - use snake_case JSON matching the actual API Gateway payload format
var snakeCaseJson = """
{
"address": {
"country": "USA",
"city": "Anytown",
"street": "Main Street",
"number": 222
},
"seller_name": "John Doe",
"property_id": "usa/anytown/main-street/222"
}
""";

var sqsEvent = new SQSEvent()
{
Records = new List<SQSEvent.SQSMessage>
{
new()
{
Body = snakeCaseJson,
MessageAttributes = new Dictionary<string, SQSEvent.MessageAttribute>
{
{ "HttpMethod", new SQSEvent.MessageAttribute { StringValue = "POST" } }
}
}
}
};

PutItemRequest? capturedRequest = null;
var mockDynamoDbClient = Substitute.ForPartsOf<AmazonDynamoDBClient>();
mockDynamoDbClient.PutItemAsync(Arg.Do<PutItemRequest>(r => capturedRequest = r))
.Returns(new PutItemResponse());

var context = TestHelpers.NewLambdaContext();

// Act
var function = new ContractEventHandler(mockDynamoDbClient);
await function.FunctionHandler(sqsEvent, context);

// Assert - verify DynamoDB item has non-empty attribute values
Assert.NotNull(capturedRequest);
Assert.False(string.IsNullOrEmpty(capturedRequest.Item["PropertyId"].S),
"PropertyId should not be null or empty - snake_case 'property_id' must map to PascalCase 'PropertyId'");
Assert.False(string.IsNullOrEmpty(capturedRequest.Item["SellerName"].S),
"SellerName should not be null or empty - snake_case 'seller_name' must map to PascalCase 'SellerName'");
Assert.NotNull(capturedRequest.Item["Address"].M);
Assert.False(string.IsNullOrEmpty(capturedRequest.Item["Address"].M["City"].S),
"Address.City should not be null or empty");
Assert.False(string.IsNullOrEmpty(capturedRequest.Item["Address"].M["Street"].S),
"Address.Street should not be null or empty");
Assert.Equal("usa/anytown/main-street/222", capturedRequest.Item["PropertyId"].S);
Assert.Equal("John Doe", capturedRequest.Item["SellerName"].S);
}

[Fact]
Expand Down Expand Up @@ -117,7 +175,7 @@
public required string seller_name { get; set; }
}

public class address

Check warning on line 178 in Unicorn.Contracts/ContractsService.Test/ContractEventHandlerTest.cs

View workflow job for this annotation

GitHub Actions / build

The type name 'address' only contains lower-cased ascii characters. Such names may become reserved for the language.

Check warning on line 178 in Unicorn.Contracts/ContractsService.Test/ContractEventHandlerTest.cs

View workflow job for this annotation

GitHub Actions / build

The type name 'address' only contains lower-cased ascii characters. Such names may become reserved for the language.

Check warning on line 178 in Unicorn.Contracts/ContractsService.Test/ContractEventHandlerTest.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The type name 'address' only contains lower-cased ascii characters. Such names may become reserved for the language.
{
public int number { get; set; }
public string? street { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>Unicorn.Contracts.ContractService.Tests</AssemblyName>
<RootNamespace>Unicorn.Contracts.ContractService.Tests</RootNamespace>
<Nullable>enable</Nullable>
Expand Down
6 changes: 5 additions & 1 deletion Unicorn.Contracts/ContractsService/Contract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Amazon.DynamoDBv2.Model;

namespace Unicorn.Contracts.ContractService;
Expand Down Expand Up @@ -61,8 +62,10 @@ public Dictionary<string, AttributeValue> ToMap()
/// </summary>
public class CreateContractRequest
{
public string? PropertyId { get; set; }
[JsonPropertyName("property_id")]
public string? PropertyId { get; set; }
public Address? Address { get; set; }
[JsonPropertyName("seller_name")]
public string? SellerName { get; set; }
}

Expand All @@ -71,5 +74,6 @@ public class CreateContractRequest
/// </summary>
public class UpdateContractRequest
{
[JsonPropertyName("property_id")]
public string? PropertyId { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<AWSProjectType>Lambda</AWSProjectType>
<AssemblyName>Unicorn.Contracts.ContractService</AssemblyName>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Globals:
Api:
OpenApiVersion: 3.0.1
Function:
Runtime: dotnet8
Runtime: dotnet10
MemorySize: 512
Timeout: 15
Tracing: Active
Expand Down
2 changes: 1 addition & 1 deletion Unicorn.Web/Common/Common.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>Unicorn.Web.Common</AssemblyName>
Expand Down
2 changes: 1 addition & 1 deletion Unicorn.Web/Infrastructure/web-service/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Globals:
Api:
OpenApiVersion: 3.0.1
Function:
Runtime: dotnet8
Runtime: dotnet10
MemorySize: 512
Timeout: 10
Tracing: Active
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>Unicorn.Web.PublicationManagerService.Tests</AssemblyName>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,67 @@ await eventBindingClient.Received(1)
Arg.Any<CancellationToken>());
}

[Fact]
public async Task Request_approval_with_snake_case_json_deserializes_property_id()
{
// Arrange - use snake_case JSON matching the actual API Gateway payload format
var snakeCaseJson = """
{
"property_id": "usa/anytown/main-street/777"
}
""";

var context = TestHelpers.NewLambdaContext();
var dynamoDbContext = Substitute.For<IDynamoDBContext>();
var eventBindingClient = Substitute.For<IAmazonEventBridge>();

var sqsEvent = new SQSEvent()
{
Records = new List<SQSEvent.SQSMessage>
{
new()
{
Body = snakeCaseJson
}
}
};

var searchResult = new List<PropertyRecord>
{
new()
{
Country = "USA",
City = "Anytown",
Street = "Main Street",
PropertyNumber = "777",
ListPrice = 2000000.00M,
Images = new() { "image1.jpg" },
Status = PropertyStatus.Pending
}
};

dynamoDbContext
.FromQueryAsync<PropertyRecord>(Arg.Any<Amazon.DynamoDBv2.DocumentModel.QueryOperationConfig>())
.Returns(TestHelpers.NewDynamoDBSearchResult(searchResult));

eventBindingClient.PutEventsAsync(Arg.Any<PutEventsRequest>(), Arg.Any<CancellationToken>())
.Returns(new PutEventsResponse { FailedEntryCount = 0 });

// Act
var function = new RequestApprovalFunction(dynamoDbContext, eventBindingClient);
await function.FunctionHandler(sqsEvent, context);

// Assert - verify the property_id was deserialized correctly and DynamoDB was queried
dynamoDbContext.Received(1)
.FromQueryAsync<PropertyRecord>(Arg.Any<Amazon.DynamoDBv2.DocumentModel.QueryOperationConfig>());

await eventBindingClient.Received(1)
.PutEventsAsync(
Arg.Is<PutEventsRequest>(r =>
r.Entries.First().Resources.Contains("usa/anytown/main-street/777")),
Arg.Any<CancellationToken>());
}

[Fact]
public async Task Do_not_publish_event_when_property_status_is_approved()
{
Expand Down Expand Up @@ -152,6 +213,6 @@ await eventBindingClient.Received(0)

public class ApiGwSqsPayload
{
[System.Text.Json.Serialization.JsonPropertyName("property_id")]
public required string PropertyId { get; set; }

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

using System.Text.Json.Serialization;

namespace Unicorn.Web.PublicationManagerService;

/// <summary>
/// Represents an event when the publication is approved.
/// </summary>
[Serializable]
public class ApprovePublicationRequest
{
{
[JsonPropertyName("property_id")]
public string PropertyId { get; set; } = null!;
}

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>Unicorn.Web.PublicationManagerService</AssemblyName>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>Unicorn.Web.SearchService.Tests</AssemblyName>
Expand Down
2 changes: 1 addition & 1 deletion Unicorn.Web/SearchService/SearchService.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>Unicorn.Web.SearchService</AssemblyName>
Expand Down
Loading
Loading