diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000..c307f2f
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,49 @@
+name: Deploy
+
+on:
+ push:
+ branches: [main]
+ workflow_dispatch:
+
+env:
+ IMAGE_NAME: ${{ secrets.DOCKERHUB_USERNAME }}/socialmedia-webapi
+
+jobs:
+ build-and-push:
+ name: Build and Push Docker Image
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v4
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v4
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags & labels)
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.IMAGE_NAME }}
+ tags: |
+ type=ref,event=branch
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=sha,prefix=sha-,format=short
+
+ - name: Build and push
+ uses: docker/build-push-action@v7
+ with:
+ context: .
+ file: Dockerfile.prod
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
diff --git a/.github/workflows/dev-ci.yml b/.github/workflows/dev-ci.yml
index fe28b2f..52af5fe 100644
--- a/.github/workflows/dev-ci.yml
+++ b/.github/workflows/dev-ci.yml
@@ -8,42 +8,44 @@ on:
workflow_dispatch:
jobs:
-unit-tests:
- name: Unit Tests
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout
- uses: actions/checkout@v6
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v4
-
- - name: Build test image
- uses: docker/build-push-action@v7
- with:
- context: .
- file: Docker/Dockerfile.test
- push: false
- load: true
- tags: socialmedia-test:${{ github.sha }}
- cache-from: type=gha,scope=test-image
- cache-to: type=gha,mode=max,scope=test-image
- - name: Run unit tests
- run: |
- mkdir -p ${{ github.workspace }}/test-results/unit
- docker run --rm \
- -v ${{ github.workspace }}/test-results/unit:/app/test-results \
- socialmedia-test:${{ github.sha }} \
- --no-build \
- --filter "Category=Unit" \
- --logger "trx;LogFileName=unit-results.trx" \
- --results-directory /app/test-results
-
- - name: Publish unit test results
- uses: dorny/test-reporter@v3
- if: always()
- with:
- name: Unit Test Results
- path: test-results/unit/*.trx
- reporter: dotnet-trx
+ unit-tests:
+ name: Unit Tests
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v5
+ with:
+ dotnet-version: 10.0.x
+
+ - name: Cache NuGet packages
+ uses: actions/cache@v5
+ with:
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget-
+
+ - name: Restore dependencies
+ run: dotnet restore
+
+ - name: Build
+ run: dotnet build --no-restore --configuration Release
+
+ - name: Run unit tests
+ run: |
+ dotnet test --no-build --configuration Release \
+ --filter "Category=Unit" \
+ --logger "trx;LogFileName=test-results.trx" \
+ --results-directory test-results/unit
+
+ - name: Publish unit test results
+ uses: dorny/test-reporter@v3
+ if: always()
+ with:
+ name: Unit Test Results
+ path: test-results/unit/*.trx
+ reporter: dotnet-trx
diff --git a/.github/workflows/main-ci.yml b/.github/workflows/main-ci.yml
index 77ed615..7bb3b2c 100644
--- a/.github/workflows/main-ci.yml
+++ b/.github/workflows/main-ci.yml
@@ -20,29 +20,31 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v4
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v5
+ with:
+ dotnet-version: 10.0.x
- - name: Build test image
- uses: docker/build-push-action@v7
+ - name: Cache NuGet packages
+ uses: actions/cache@v5
with:
- context: .
- file: Docker/Dockerfile.test
- push: false
- load: true
- tags: socialmedia-test:${{ github.sha }}
- cache-from: type=gha,scope=test-image
- cache-to: type=gha,mode=max,scope=test-image
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget-
+
+ - name: Restore dependencies
+ run: dotnet restore
+
+ - name: Build
+ run: dotnet build --no-restore --configuration Release
+
- name: Run unit tests
run: |
- mkdir -p ${{ github.workspace }}/test-results/unit
- docker run --rm \
- -v ${{ github.workspace }}/test-results/unit:/app/test-results \
- socialmedia-test:${{ github.sha }} \
- --no-build \
+ dotnet test --no-build --configuration Release \
--filter "Category=Unit" \
- --logger "trx;LogFileName=unit-results.trx" \
- --results-directory /app/test-results
+ --logger "trx;LogFileName=test-results.trx" \
+ --results-directory test-results/unit
- name: Publish unit test results
uses: dorny/test-reporter@v3
@@ -72,34 +74,10 @@ jobs:
push: false
load: true
tags: socialmedia-test:${{ github.sha }}
- cache-from: type=gha,scope=test-image
- cache-to: type=gha,mode=max,scope=test-image
-
- - name: Cache Testcontainers Docker images
- id: cache-docker
- uses: actions/cache@v5
- with:
- path: /tmp/docker-images
- key: docker-images-${{ runner.os }}-mssql-2022-ryuk-0.14.0
-
- - name: Load cached images
- if: steps.cache-docker.outputs.cache-hit == 'true'
- run: |
- docker load < /tmp/docker-images/mssql.tar
- docker load < /tmp/docker-images/ryuk.tar
-
- - name: Pull and save images
- if: steps.cache-docker.outputs.cache-hit != 'true'
- run: |
- docker pull mcr.microsoft.com/mssql/server:2022-latest
- docker pull testcontainersofficial/ryuk:0.14.0
- mkdir -p /tmp/docker-images
- docker save mcr.microsoft.com/mssql/server:2022-latest > /tmp/docker-images/mssql.tar
- docker save testcontainersofficial/ryuk:0.14.0 > /tmp/docker-images/ryuk.tar
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
- name: Run integration tests
- env:
- TESTCONTAINERS_RYUK_DISABLED: "false"
run: |
mkdir -p ${{ github.workspace }}/test-results/integration
docker run --rm \
diff --git a/Docker/Dockerfile.prod b/Docker/Dockerfile.prod
index ed30be8..c6b765f 100644
--- a/Docker/Dockerfile.prod
+++ b/Docker/Dockerfile.prod
@@ -25,7 +25,6 @@ COPY . .
RUN --mount=type=cache,id=nuget-http,target=/root/.local/share/NuGet/http-cache \
--mount=type=cache,id=nuget-global,target=$NUGET_PACKAGES \
dotnet build ./SocialMedia.slnx \
- --no-restore \
-c Release
diff --git a/Docker/Dockerfile.test b/Docker/Dockerfile.test
index 6fef0a1..fa933e9 100644
--- a/Docker/Dockerfile.test
+++ b/Docker/Dockerfile.test
@@ -1,7 +1,9 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
+
WORKDIR /app
ENV NUGET_PACKAGES=/root/.nuget/packages
+ENV ASPNETCORE_ENVIRONMENT=Testing
COPY Directory.Packages.props .
COPY Directory.Build.props .
@@ -13,16 +15,13 @@ COPY ./tests/SocialMedia.Application.UnitTests/*.csproj ./tests/SocialMedia.Ap
COPY ./tests/SocialMedia.IntegrationTests/*.csproj ./tests/SocialMedia.IntegrationTests/
COPY ./SocialMedia.slnx .
-RUN --mount=type=cache,id=nuget-http,target=/root/.local/share/NuGet/http-cache \
- --mount=type=cache,id=nuget-global,target=$NUGET_PACKAGES \
+RUN --mount=type=cache,target=$NUGET_PACKAGES \
dotnet restore ./SocialMedia.slnx
COPY . .
-RUN --mount=type=cache,id=nuget-http,target=/root/.local/share/NuGet/http-cache \
- --mount=type=cache,id=nuget-global,target=$NUGET_PACKAGES \
+RUN --mount=type=cache,target=$NUGET_PACKAGES \
dotnet build ./SocialMedia.slnx \
- --no-restore \
-c Debug
ENTRYPOINT ["dotnet", "test"]
diff --git a/src/SocialMedia.WebApi/SocialMedia.WebApi.csproj b/src/SocialMedia.WebApi/SocialMedia.WebApi.csproj
index 9307c6f..ed77b38 100644
--- a/src/SocialMedia.WebApi/SocialMedia.WebApi.csproj
+++ b/src/SocialMedia.WebApi/SocialMedia.WebApi.csproj
@@ -28,8 +28,10 @@
-
-
+
+
+
+
diff --git a/src/SocialMedia.WebApi/appsettings.Testing.json b/src/SocialMedia.WebApi/appsettings.Testing.json
new file mode 100644
index 0000000..a4c2594
--- /dev/null
+++ b/src/SocialMedia.WebApi/appsettings.Testing.json
@@ -0,0 +1,59 @@
+{
+ "Serilog": {
+ "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.Seq"],
+ "MinimumLevel": {
+ "Default": "Information",
+ "Override": {
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information",
+ "System": "Warning"
+ }
+ },
+ "Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"],
+ "WriteTo": [
+ {
+ "Name": "Console",
+ "Args": {
+ "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}"
+ }
+ },
+ {
+ "Name": "Seq",
+ "Args": {
+ "serverUrl": "http://social-seq:5341"
+ }
+ }
+ ]
+ },
+ "ConnectionStrings": {
+ "sqlServerConnectionString": "Data Source=;Initial Catalog=SocialMedia;User ID=sa;Password=;Connect Timeout=30;Trust Server Certificate=True;Authentication=SqlPassword;"
+ },
+ "jwt": {
+ "Issuer": "SocialMedia.WebApi",
+ "Audience": "SocialMedia.Frontend",
+ "Lifetime": 60,
+ "RefreshTokenLifetime": 120,
+ "SigningKey": "your-secure-signing-key-for-jwt-tokensaklhg"
+ },
+ "email": {
+ "Smtp": "smtp.gmail.com",
+ "Port": 587,
+ "Username": "your-email@gmail.com",
+ "Password": "your-app-password",
+ "From": "your-email@gmail.com",
+ "BaseUrl": "https://localhost:5000",
+ "EmailVerificationPath": "/api/auth/verify-email?token={token}",
+ "PasswordResetPath": "/api/auth/reset-password?token={token}"
+ },
+ "supabase": {
+ "Url": "https://test-supabase-url.supabase.co",
+ "Key": "test-supabase-key"
+ },
+ "durations": {
+ "EmailVerificationTokenExpiryMinutes": 30,
+ "PasswordResetTokenExpiryMinutes": 30
+ },
+ "FileUpload": {
+ "Provider": "Server"
+ }
+}
diff --git a/tests/SocialMedia.IntegrationTests/Infrastructure/IntegrationTestWebAppFactory.cs b/tests/SocialMedia.IntegrationTests/Infrastructure/IntegrationTestWebAppFactory.cs
index 7659e6b..3eb3e96 100644
--- a/tests/SocialMedia.IntegrationTests/Infrastructure/IntegrationTestWebAppFactory.cs
+++ b/tests/SocialMedia.IntegrationTests/Infrastructure/IntegrationTestWebAppFactory.cs
@@ -5,6 +5,7 @@
using Microsoft.AspNetCore.TestHost;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
@@ -29,31 +30,35 @@ public class IntegrationTestWebAppFactory : WebApplicationFactory, IAsy
.WithCleanUp(true)
.Build();
public HttpClient HttpClient { get; private set; } = null!;
- private SqlConnection _dbConnection;
+ private SqlConnection _dbConnection = null!;
private Respawner _respawner = null!;
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
+ var testConnectionString = _msSqlContainer.GetConnectionString();
+
builder.ConfigureTestServices(services =>
{
services.RemoveAll(typeof(DbContextOptions));
services.AddDbContext(options =>
{
- options.UseSqlServer(_msSqlContainer.GetConnectionString());
+ options.UseSqlServer(testConnectionString);
});
services.RemoveAll(typeof(IHostedService));
services.RemoveAll(typeof(ISqlConnectionFactory));
- services.AddSingleton(new SqlConnectionFactory(_msSqlContainer.GetConnectionString()));
+ services.AddSingleton(new SqlConnectionFactory(testConnectionString));
});
}
public async ValueTask InitializeAsync()
{
await _msSqlContainer.StartAsync();
+ var testConnectionString = _msSqlContainer.GetConnectionString();
+
HttpClient = CreateClient();
- _dbConnection = new SqlConnection(_msSqlContainer.GetConnectionString());
+ _dbConnection = new SqlConnection(testConnectionString);
await _dbConnection.OpenAsync();
_respawner = await Respawner.CreateAsync(_dbConnection, new RespawnerOptions
{