Commit 0c3d4de9 authored by Ahmad Abd's avatar Ahmad Abd

Initial commit

parents
Pipeline #203 canceled with stages
# ==================================
# Visual Studio & .NET Ignore File
# ==================================
# 1. Build results (These are generated automatically when building, do not push them!)
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# 2. Visual Studio user-specific files (Local settings for your specific computer)
.vs/
*.user
*.userosscache
*.sln.docstates
*.suo
*.userprefs
*.vssscc
# 3. Test results (Don't push test outputs, the CI/CD pipeline will generate its own)
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*.trx
*.testsettings
# 4. Environment variables and secrets (Security best practice)
.env
appsettings.Development.json
\ No newline at end of file
# 1. Specify the environment
# We use the official Microsoft .NET 8 SDK image to run our commands
image: mcr.microsoft.com/dotnet/sdk:8.0
# 2. Define the stages of the pipeline
# These run in order: first build, then test.
stages:
- build
- test
# 3. The Build Job
# This ensures the code actually compiles before we even try to test it
build_project:
stage: build
script:
- echo "Building the ASP.NET Core 8 Solution..."
- dotnet build ECommerceApp.Solution.sln --configuration Release
# 4. The Test Job
# This runs the xUnit unit and integration tests we created
run_tests:
stage: test
script:
- echo "Running xUnit Tests..."
- dotnet test ECommerceApp.Solution.sln --configuration Release --logger "console;verbosity=detailed"
# Optional: This tells the pipeline to stop if the tests fail
allow_failure: false
\ No newline at end of file
<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="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ECommerceApp\ECommerceApp.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
</Project>
using ECommerceApp.Models;
using ECommerceApp.Services;
using Moq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ECommerceApp.Tests.IntegrationTests
{
public class OrderCheckoutServiceIntegrationTests
{
[Fact]
public async Task ProcessOrderAsync_IntegratesWithDatabaseService_AssignsId()
{
// 1. ARRANGE
// We use the ACTUAL FakeDatabaseService, not a Moq object
var realFakeDbService = new FakeDatabaseService();
// We inject the actual service into the checkout service
var checkoutService = new OrderCheckoutService(realFakeDbService);
// 2. ACT
// This will take at least 500ms because it is running the actual Task.Delay
var result = await checkoutService.ProcessOrderAsync(50m, "WINTER20");
// 3. ASSERT
Assert.Equal(40m, result.FinalTotal); // 50 - 20% = 40
// The crucial integration check: Did the database service successfully
// process the order and attach the simulated database ID (999)?
Assert.Equal(999, result.Id);
}
}
}
using ECommerceApp.Models;
using ECommerceApp.Services;
using Moq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ECommerceApp.Tests.UnitTests
{
public class OrderCheckoutServiceUnitTests
{
[Fact]
public async Task ProcessOrderAsync_WithSummer10Code_Applies10PercentDiscount()
{
// 1. ARRANGE (Set up the test)
// We create a mock (a fake version) of the IDatabaseService
var mockDbService = new Mock<IDatabaseService>();
// We tell the mock to just return a dummy ID immediately if called
mockDbService
.Setup(db => db.SaveOrderAsync(It.IsAny<Order>()))
.ReturnsAsync(1);
// We inject the mock into our service
var checkoutService = new OrderCheckoutService(mockDbService.Object);
// 2. ACT (Run the method being tested)
var result = await checkoutService.ProcessOrderAsync(100m, "SUMMER10");
// 3. ASSERT (Verify the results)
Assert.Equal(90m, result.FinalTotal); // 100 - 10% = 90
Assert.Equal("SUMMER10", result.DiscountCode);
// Verify that the service actually tried to save the order once
mockDbService.Verify(db => db.SaveOrderAsync(It.IsAny<Order>()), Times.Once);
}
[Fact]
public async Task ProcessOrderAsync_WithNoCode_AppliesZeroDiscount()
{
// 1. ARRANGE
var mockDbService = new Mock<IDatabaseService>();
var checkoutService = new OrderCheckoutService(mockDbService.Object);
// 2. ACT
var result = await checkoutService.ProcessOrderAsync(100m, null);
// 3. ASSERT
Assert.Equal(100m, result.FinalTotal); // No discount
}
}
}

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.11.35312.102
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ECommerceApp", "ECommerceApp\ECommerceApp.csproj", "{9A58B8CD-F227-46E6-AF2A-3A08754576D2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ECommerceApp.Tests", "ECommerceApp.Tests\ECommerceApp.Tests.csproj", "{97B94E99-3794-4F3D-B200-5CCEE500B7F0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9A58B8CD-F227-46E6-AF2A-3A08754576D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9A58B8CD-F227-46E6-AF2A-3A08754576D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A58B8CD-F227-46E6-AF2A-3A08754576D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A58B8CD-F227-46E6-AF2A-3A08754576D2}.Release|Any CPU.Build.0 = Release|Any CPU
{97B94E99-3794-4F3D-B200-5CCEE500B7F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{97B94E99-3794-4F3D-B200-5CCEE500B7F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{97B94E99-3794-4F3D-B200-5CCEE500B7F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{97B94E99-3794-4F3D-B200-5CCEE500B7F0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6D8CBDE1-39D9-4891-B63A-432E606E43E6}
EndGlobalSection
EndGlobal
using Microsoft.AspNetCore.Mvc;
namespace ECommerceApp.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
</Project>
@ECommerceApp_HostAddress = http://localhost:5241
GET {{ECommerceApp_HostAddress}}/weatherforecast/
Accept: application/json
###
namespace ECommerceApp.Models
{
public class Order
{
public int Id { get; set; }
public decimal SubTotal { get; set; }
public string? DiscountCode { get; set; }
public decimal FinalTotal { get; set; }
}
}
using ECommerceApp.Services;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddScoped<UserRegistrationService>();
var app = builder.Build();
app.MapGet("/test-registration", (string email, UserRegistrationService registrationService) =>
{
bool isValid = registrationService.ValidateAndRegister(email);
if (isValid)
return Results.Ok($"Success! Check the command prompt for the log.");
else
return Results.BadRequest($"Failed! Check the command prompt for the warning.");
});
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:52425",
"sslPort": 44324
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5241",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7007;http://localhost:5241",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
using ECommerceApp.Models;
namespace ECommerceApp.Services
{
public class FakeDatabaseService : IDatabaseService
{
public async Task<int> SaveOrderAsync(Order order)
{
// Simulate a 500 millisecond database/network delay
await Task.Delay(500);
// Assign a fake static ID to simulate an auto-incrementing database column
order.Id = 999;
return order.Id;
}
}
}
using ECommerceApp.Models;
namespace ECommerceApp.Services
{
public interface IDatabaseService
{
// We use Task to simulate an async database call
Task<int> SaveOrderAsync(Order order);
}
}
using ECommerceApp.Models;
namespace ECommerceApp.Services
{
public class OrderCheckoutService
{
private readonly IDatabaseService _dbService;
public OrderCheckoutService(IDatabaseService dbService)
{
_dbService = dbService;
}
public async Task<Order> ProcessOrderAsync(decimal subTotal, string? discountCode)
{
var order = new Order
{
SubTotal = subTotal,
DiscountCode = discountCode
};
// 1. Simple internal discount logic
decimal discountPercentage = 0;
if (discountCode == "SUMMER10")
{
discountPercentage = 10;
}
else if (discountCode == "WINTER20")
{
discountPercentage = 20;
}
// 2. Apply math logic
decimal discountAmount = subTotal * (discountPercentage / 100m);
order.FinalTotal = subTotal - discountAmount;
// 3. Simulate saving to the database
await _dbService.SaveOrderAsync(order);
return order;
}
}
}
namespace ECommerceApp.Services
{
public class UserRegistrationService
{
// 1. Declare the logger
private readonly ILogger<UserRegistrationService> _logger;
// 2. Inject the logger via constructor
public UserRegistrationService(ILogger<UserRegistrationService> logger)
{
_logger = logger;
}
public bool ValidateAndRegister(string email)
{
// Log that a process has started (Information)
_logger.LogInformation("Attempting to register user with email: {Email} at {Date}", email,DateTime.Now);
// Simple business logic: Is it a valid email?
if (string.IsNullOrWhiteSpace(email) || !email.Contains("@"))
{
// Log a warning if the user did something wrong (Warning)
_logger.LogWarning("Registration failed. Invalid email format provided: {Email} at {Date}", email,DateTime.Now);
return false;
}
// Log successful completion (Information)
_logger.LogInformation("User {Email} successfully registered.", email);
return true;
}
}
}
namespace ECommerceApp
{
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment