Skip to content

Commit 15d1d95

Browse files
committed
Add GitHub Actions workflows for Azure deployment and .NET CI, and update error page structure
1 parent 54917ec commit 15d1d95

File tree

8 files changed

+279
-7
lines changed

8 files changed

+279
-7
lines changed

.github/workflows/deploy.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Azure Bicep
2+
3+
on:
4+
workflow_dispatch
5+
6+
env:
7+
targetEnv: dev
8+
9+
jobs:
10+
build-and-deploy:
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: read
14+
pages: write
15+
id-token: write
16+
steps:
17+
# Checkout code
18+
- uses: actions/checkout@main
19+
20+
# Log into Azure
21+
- uses: azure/[email protected]
22+
with:
23+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
24+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
25+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
26+
enable-AzPSSession: true
27+
28+
# Deploy ARM template
29+
- name: Run ARM deploy
30+
uses: azure/arm-deploy@v1
31+
with:
32+
subscriptionId: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
33+
resourceGroupName: ${{ secrets.AZURE_RG }}
34+
template: ./src/InfrastructureAsCode/main.bicep
35+
parameters: environment=${{ env.targetEnv }}

.github/workflows/dotnet-deploy-1.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: .NET CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
paths:
7+
- src/Application/**
8+
pull_request:
9+
branches: [ main ]
10+
paths:
11+
- src/Application/**
12+
# Allows you to run this workflow manually from the Actions tab
13+
workflow_dispatch:
14+
jobs:
15+
build:
16+
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- uses: actions/checkout@v3
21+
- name: Setup .NET
22+
uses: actions/setup-dotnet@v3
23+
with:
24+
dotnet-version: '8.0.x'
25+
26+
- name: Restore dependencies
27+
run: dotnet restore ./src/Application/src/RazorPagesTestSample/RazorPagesTestSample.csproj
28+
- name: Build
29+
run: dotnet build --no-restore ./src/Application/src/RazorPagesTestSample/RazorPagesTestSample.csproj
30+
- name: Test
31+
run: dotnet test --no-build --verbosity normal ./src/Application/tests/RazorPagesTestSample.Tests/RazorPagesTestSample.Tests.csproj

.vscode/tasks.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"label": "Save All Files",
6+
"command": "${command:workbench.action.files.saveAll}",
7+
"type": "shell",
8+
"problemMatcher": []
9+
},
10+
{
11+
"label": "Build Project",
12+
"dependsOn": "Save All Files",
13+
"command": "dotnet build",
14+
"type": "shell",
15+
"group": {
16+
"kind": "build",
17+
"isDefault": true
18+
},
19+
"problemMatcher": "$msCompile"
20+
}
21+
]
22+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Use the official .NET 8 SDK image as a build stage
2+
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
3+
WORKDIR /app
4+
5+
# Copy the project file and restore dependencies
6+
COPY *.csproj .
7+
RUN dotnet restore
8+
9+
# Copy the rest of the application code
10+
COPY . .
11+
12+
# Build the application
13+
RUN dotnet publish -c Release -o out
14+
15+
# Use the official .NET 8 runtime image as a runtime stage
16+
FROM mcr.microsoft.com/dotnet/aspnet:8.0
17+
WORKDIR /app
18+
COPY --from=build /app/out .
19+
20+
# Set the environment variable for ASP.NET Core HTTP ports
21+
ENV ASPNETCORE_HTTP_PORTS=80
22+
23+
# Set the entry point for the application
24+
ENTRYPOINT ["dotnet", "RazorPagesTestSample.dll"]

src/Application/src/RazorPagesTestSample/Pages/Error.cshtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
}
66

77
<h1 class="text-danger">Error.</h1>
8-
<h2 class="text-danger">An error occurred while processing your request.</h2>
8+
<h3 class="text-danger">An error occurred while processing your request.</h3>
99

1010
@if (Model.ShowRequestId)
1111
{

src/Application/tests/RazorPagesTestSample.Tests/UnitTests/DataAccessLayerTest.cs

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.EntityFrameworkCore;
55
using Xunit;
66
using RazorPagesTestSample.Data;
7+
using System.ComponentModel.DataAnnotations;
78

89
namespace RazorPagesTestSample.Tests.UnitTests
910
{
@@ -25,7 +26,7 @@ public async Task GetMessagesAsync_MessagesAreReturned()
2526
// Assert
2627
var actualMessages = Assert.IsAssignableFrom<List<Message>>(result);
2728
Assert.Equal(
28-
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
29+
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
2930
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
3031
}
3132
}
@@ -77,7 +78,7 @@ public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound()
7778
await db.AddRangeAsync(seedMessages);
7879
await db.SaveChangesAsync();
7980
var recId = 1;
80-
var expectedMessages =
81+
var expectedMessages =
8182
seedMessages.Where(message => message.Id != recId).ToList();
8283
#endregion
8384

@@ -90,7 +91,7 @@ public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound()
9091
// Assert
9192
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
9293
Assert.Equal(
93-
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
94+
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
9495
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
9596
#endregion
9697
}
@@ -121,10 +122,42 @@ public async Task DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound()
121122
// Assert
122123
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
123124
Assert.Equal(
124-
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
125+
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
125126
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
126127
}
127128
}
129+
130+
131+
132+
//Generate a unit test theory to generate messages of various lengths including 250 and try to validate the message object.
133+
[Theory]
134+
[InlineData(150, true)]
135+
[InlineData(199, true)]
136+
[InlineData(200, true)]
137+
[InlineData(201, true)]
138+
[InlineData(249, true)]
139+
[InlineData(250, true)]
140+
[InlineData(251, false)]
141+
[InlineData(300, false)]
142+
public async Task AddMessageAsync_TestMessageLength(int messageLength, bool expectedValidMessage)
143+
{
144+
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
145+
{
146+
// Arrange
147+
var recId = 10;
148+
var expectedMessage = new Message() { Id = recId, Text = new string('X', messageLength) };
149+
150+
// Act
151+
var isValidMessage = Validator.TryValidateObject(expectedMessage, new ValidationContext(expectedMessage), null, validateAllProperties: true);
152+
153+
// Simulate an asynchronous operation
154+
await Task.Delay(1);
155+
156+
// Assert
157+
Assert.Equal(expectedValidMessage, isValidMessage);
158+
}
159+
}
160+
128161
#endregion
129162
}
130163
}

src/Application/tests/RazorPagesTestSample.Tests/UnitTests/MessageTests.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.ComponentModel.DataAnnotations;
22
using RazorPagesTestSample.Data;
33
using Xunit;
4+
using System.Collections.Generic;
45

56
namespace RazorPagesTestSample.Tests.UnitTests
67
{
@@ -42,5 +43,43 @@ public void MessageText_ShouldBeValid_WhenWithin250Characters()
4243
// Assert
4344
Assert.True(isValid);
4445
}
46+
47+
//test the insertion of a message of length 150
48+
[Fact]
49+
public void MessageText_ShouldBeValid_WhenWithin150Characters()
50+
{
51+
// Arrange
52+
var message = new Message
53+
{
54+
Text = new string('a', 150) // 150 characters
55+
};
56+
var validationContext = new ValidationContext(message);
57+
var validationResults = new List<ValidationResult>();
58+
59+
// Act
60+
var isValid = Validator.TryValidateObject(message, validationContext, validationResults, true);
61+
62+
// Assert
63+
Assert.True(isValid);
64+
}
65+
66+
//test a message of length 249
67+
[Fact]
68+
public void MessageText_ShouldBeValid_WhenWithin249Characters()
69+
{
70+
// Arrange
71+
var message = new Message
72+
{
73+
Text = new string('a', 249) // 249 characters
74+
};
75+
var validationContext = new ValidationContext(message);
76+
var validationResults = new List<ValidationResult>();
77+
78+
// Act
79+
var isValid = Validator.TryValidateObject(message, validationContext, validationResults, true);
80+
81+
// Assert
82+
Assert.True(isValid);
83+
}
4584
}
4685
}

src/InfrastructureAsCode/main.bicep

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,98 @@ var webAppName = '${uniqueString(resourceGroup().id)}-${environment}'
88
var appServicePlanName = '${uniqueString(resourceGroup().id)}-mpnp-asp'
99
var logAnalyticsName = '${uniqueString(resourceGroup().id)}-mpnp-la'
1010
var appInsightsName = '${uniqueString(resourceGroup().id)}-mpnp-ai'
11-
var sku = 'S1'
11+
var sku = 'P0V3'
1212
var registryName = '${uniqueString(resourceGroup().id)}mpnpreg'
1313
var registrySku = 'Standard'
1414
var imageName = 'techexcel/dotnetcoreapp'
1515
var startupCommand = ''
1616

17-
// TODO: complete this script
17+
18+
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = {
19+
name: logAnalyticsName
20+
location: location
21+
properties: {
22+
sku: {
23+
name: 'PerGB2018'
24+
}
25+
retentionInDays: 90
26+
workspaceCapping: {
27+
dailyQuotaGb: 1
28+
}
29+
}
30+
}
31+
32+
resource appInsights 'Microsoft.Insights/components@2020-02-02-preview' = {
33+
name: appInsightsName
34+
location: location
35+
kind: 'web'
36+
properties: {
37+
Application_Type: 'web'
38+
WorkspaceResourceId: logAnalyticsWorkspace.id
39+
}
40+
}
41+
42+
resource containerRegistry 'Microsoft.ContainerRegistry/registries@2020-11-01-preview' = {
43+
name: registryName
44+
location: location
45+
sku: {
46+
name: registrySku
47+
}
48+
properties: {
49+
adminUserEnabled: true
50+
}
51+
}
52+
53+
resource appServicePlan 'Microsoft.Web/serverFarms@2022-09-01' = {
54+
name: appServicePlanName
55+
location: location
56+
kind: 'linux'
57+
properties: {
58+
reserved: true
59+
}
60+
sku: {
61+
name: sku
62+
}
63+
}
64+
65+
resource appServiceApp 'Microsoft.Web/sites@2020-12-01' = {
66+
name: webAppName
67+
location: location
68+
properties: {
69+
serverFarmId: appServicePlan.id
70+
httpsOnly: true
71+
clientAffinityEnabled: false
72+
siteConfig: {
73+
linuxFxVersion: 'DOCKER|${containerRegistry.name}.azurecr.io/${uniqueString(resourceGroup().id)}/${imageName}'
74+
http20Enabled: true
75+
minTlsVersion: '1.2'
76+
appCommandLine: startupCommand
77+
appSettings: [
78+
{
79+
name: 'WEBSITES_ENABLE_APP_SERVICE_STORAGE'
80+
value: 'false'
81+
}
82+
{
83+
name: 'DOCKER_REGISTRY_SERVER_URL'
84+
value: 'https://${containerRegistry.name}.azurecr.io'
85+
}
86+
{
87+
name: 'DOCKER_REGISTRY_SERVER_USERNAME'
88+
value: containerRegistry.name
89+
}
90+
{
91+
name: 'DOCKER_REGISTRY_SERVER_PASSWORD'
92+
value: containerRegistry.listCredentials().passwords[0].value
93+
}
94+
{
95+
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
96+
value: appInsights.properties.InstrumentationKey
97+
}
98+
]
99+
}
100+
}
101+
}
102+
103+
output application_name string = appServiceApp.name
104+
output application_url string = appServiceApp.properties.hostNames[0]
105+
output container_registry_name string = containerRegistry.name

0 commit comments

Comments
 (0)