├── docs ├── 01_Introduction.md ├── Images │ ├── Living_Documentation.png │ ├── ParkingCostCalculator_OK.png │ ├── ParkingCostCalculator_Mockup.png │ └── ParkingCostCalculator_WelcomePage.png └── 02_Requirements.md ├── .dockerignore ├── src ├── ParkCostCalc.Infrastructure │ ├── Class1.cs │ └── ParkCostCalc.Infrastructure.csproj ├── ParkCostCalc.Core │ ├── Services │ │ ├── CostCalculators │ │ │ ├── ICostCalc.cs │ │ │ ├── Economy.cs │ │ │ ├── LongTermGarage.cs │ │ │ ├── LongTermSurface.cs │ │ │ ├── CalculatorFactory.cs │ │ │ ├── Valet.cs │ │ │ ├── CalculatorBase.cs │ │ │ └── ShortTerm.cs │ │ └── ParkCostCalcService.cs │ ├── Models │ │ ├── ParkTypeEnum.cs │ │ ├── ParkRequest.cs │ │ ├── CostDetails.cs │ │ └── Contact.cs │ ├── Interfaces │ │ └── IParkCostCalcService.cs │ ├── ParkCostCalc.Core.csproj │ └── Helpers │ │ └── MinuteConvertor.cs └── ParkCostCalc.Api │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── Program.cs │ ├── ParkCostCalc.Api.csproj │ ├── Controllers │ └── CostCalculatorController.cs │ ├── Properties │ └── launchSettings.json │ └── Startup.cs ├── tests ├── ParkCostCalc.AcceptanceTests │ ├── specflow.json │ ├── Models │ │ ├── CostResponse.cs │ │ ├── ParkTypeEnum.cs │ │ └── Contact.cs │ ├── Properties │ │ ├── launchSettings.json │ │ └── AssemblyInfo.cs │ ├── Drivers │ │ └── CostCalculator │ │ │ ├── ICostCalculatorDriver.cs │ │ │ └── CostCalculatorApiDriver.cs │ ├── Support │ │ ├── DependencyRegister.cs │ │ └── Hooks.cs │ ├── Dsl │ │ └── CostCalculatorDsl.cs │ ├── Features │ │ ├── ShortTerm.feature │ │ ├── Valet.feature │ │ ├── Economy.feature │ │ ├── LongTermSurface.feature │ │ └── LongTermGarage.feature │ ├── ParkCostCalc.AcceptanceTests.csproj │ ├── StepDefinitions │ │ └── CostCalculatorSteps.cs │ └── Helpers │ │ └── Parser.cs └── ParkCostCalc.UnitTests │ ├── ParkCostCalc.UnitTests.csproj │ └── CostCalculators │ ├── ShortTermTest.cs │ ├── ValetTest.cs │ ├── LongTermSurfaceTest.cs │ ├── EconomyTest.cs │ └── LongTermGarageTest.cs ├── .github └── workflows │ └── ci-cd.yaml ├── .gitattributes ├── ParkingCostCalculator.sln ├── README.md └── .gitignore /docs/01_Introduction.md: -------------------------------------------------------------------------------- 1 | #Introduction 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/Images/Living_Documentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tawfiknouri/BDD-TDD_ParkingCostCalculator_SpecFlow/HEAD/docs/Images/Living_Documentation.png -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | .env 3 | .git 4 | .gitignore 5 | .vs 6 | .vscode 7 | docker-compose.yml 8 | docker-compose.*.yml 9 | */bin 10 | */obj 11 | -------------------------------------------------------------------------------- /docs/Images/ParkingCostCalculator_OK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tawfiknouri/BDD-TDD_ParkingCostCalculator_SpecFlow/HEAD/docs/Images/ParkingCostCalculator_OK.png -------------------------------------------------------------------------------- /src/ParkCostCalc.Infrastructure/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ParkCostCalc.Infrastructure 4 | { 5 | public class Class1 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/Images/ParkingCostCalculator_Mockup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tawfiknouri/BDD-TDD_ParkingCostCalculator_SpecFlow/HEAD/docs/Images/ParkingCostCalculator_Mockup.png -------------------------------------------------------------------------------- /docs/Images/ParkingCostCalculator_WelcomePage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tawfiknouri/BDD-TDD_ParkingCostCalculator_SpecFlow/HEAD/docs/Images/ParkingCostCalculator_WelcomePage.png -------------------------------------------------------------------------------- /src/ParkCostCalc.Infrastructure/ParkCostCalc.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/ParkCostCalc.Core/Services/CostCalculators/ICostCalc.cs: -------------------------------------------------------------------------------- 1 | namespace ParkCostCalc.Core.Services.CostCalculators 2 | { 3 | public interface ICostCalc 4 | { 5 | public decimal CalculateCost(double totalMinutes); 6 | } 7 | } -------------------------------------------------------------------------------- /docs/02_Requirements.md: -------------------------------------------------------------------------------- 1 | Parking Calc GUI: 2 | ![alt text](./Images/ParkingCostCalculator_WelcomePage.png "Parking Cost Calculator Welcome Page") 3 | 4 | Parking Calc GUI: 5 | ![alt text](./Images/ParkingCostCalculator_OK.png "Parking Cost Calculator Page") -------------------------------------------------------------------------------- /src/ParkCostCalc.Api/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/specflow.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "feature": "en" 4 | }, 5 | "allowDebugGeneratedFiles": true, 6 | "livingDocGenerator": { 7 | "enabled": true, 8 | "filepath": "FeatureData.json" 9 | } 10 | } -------------------------------------------------------------------------------- /src/ParkCostCalc.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /src/ParkCostCalc.Core/Models/ParkTypeEnum.cs: -------------------------------------------------------------------------------- 1 | namespace ParkCostCalc.Core.Models 2 | { 3 | public enum ParkTypeEnum 4 | { 5 | Valet, 6 | ShortTerm, 7 | LongTermGarage, 8 | LongTermSurface, 9 | Economy 10 | } 11 | } -------------------------------------------------------------------------------- /src/ParkCostCalc.Core/Interfaces/IParkCostCalcService.cs: -------------------------------------------------------------------------------- 1 | using ParkCostCalc.Core.Models; 2 | 3 | namespace ParkCostCalc.Core.Interfaces 4 | { 5 | public interface IParkCostCalcService 6 | { 7 | public CostDetails CalculateCost(ParkRequest parkRequest); 8 | } 9 | } -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/Models/CostResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace ParkCostCalc.AcceptanceTests.Models 4 | { 5 | public class CostResponse 6 | { 7 | [JsonProperty("cost")] 8 | public string Cost {get; set;} 9 | } 10 | } -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "ParkCostCalc.Core.Specs": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "MSBUILDSINGLELOADCONTEXT": "1" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/Models/ParkTypeEnum.cs: -------------------------------------------------------------------------------- 1 | namespace ParkCostCalc.AcceptanceTests.Models 2 | { 3 | public enum ParkTypeEnum 4 | { 5 | Valet, 6 | ShortTerm, 7 | LongTermGarage, 8 | LongTermSurface, 9 | Economy 10 | } 11 | } -------------------------------------------------------------------------------- /src/ParkCostCalc.Core/ParkCostCalc.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/ParkCostCalc.Core/Models/ParkRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ParkCostCalc.Core.Models 4 | { 5 | public class ParkRequest 6 | { 7 | public ParkTypeEnum? ParkType { get; set; } 8 | public DateTime? EntryDate { get; set; } 9 | public DateTime? ExitDate { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/Drivers/CostCalculator/ICostCalculatorDriver.cs: -------------------------------------------------------------------------------- 1 | using ParkCostCalc.AcceptanceTests.Models; 2 | 3 | namespace ParkCostCalc.AcceptanceTests.Drivers.CostCalculator 4 | { 5 | public interface ICostCalculatorDriver 6 | { 7 | public decimal CalculateCost(ParkTypeEnum parkingLot, string duration); 8 | } 9 | } -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | using NUnit.Framework; 4 | 5 | 6 | [assembly: Parallelizable(ParallelScope.Fixtures)] 7 | 8 | [assembly: LevelOfParallelism(5)] 9 | 10 | [assembly: ComVisible(false)] 11 | 12 | [assembly: Guid("2ff50688-c53f-4b11-8d86-6ff1549b88b3")] -------------------------------------------------------------------------------- /src/ParkCostCalc.Core/Models/CostDetails.cs: -------------------------------------------------------------------------------- 1 | namespace ParkCostCalc.Core.Models 2 | { 3 | public class CostDetails 4 | { 5 | public decimal Cost { get; set; } 6 | public string Currency { get; set; } = "€"; 7 | public double Days { get; set; } 8 | public double Hours { get; set; } 9 | public double Minutes { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/ParkCostCalc.Core/Models/Contact.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ParkCostCalc.Core.Models 4 | { 5 | public class Contact 6 | { 7 | public int Id { get; set; } 8 | public string Name { get; set; } 9 | public string Email { get; set; } 10 | public string Subject { get; set; } 11 | public string Message { get; set; } 12 | public DateTime? CreateDate { get; set; } = DateTime.Now; 13 | } 14 | } -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/Support/DependencyRegister.cs: -------------------------------------------------------------------------------- 1 | using BoDi; 2 | using ParkCostCalc.AcceptanceTests.Drivers.CostCalculator; 3 | 4 | namespace ParkCostCalc.AcceptanceTests.Support 5 | { 6 | public static class DependencyRegister 7 | { 8 | public static void RegisterDependencies(IObjectContainer objectContainer) 9 | { 10 | objectContainer.RegisterTypeAs(); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/Models/Contact.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ParkCostCalc.AcceptanceTests.Models 4 | { 5 | public class Contact 6 | { 7 | public int Id { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public string Email { get; set; } 12 | 13 | public string Subject { get; set; } 14 | 15 | public string Message { get; set; } 16 | 17 | public DateTime? CreateDate { get; set; } = DateTime.Now; 18 | } 19 | } -------------------------------------------------------------------------------- /src/ParkCostCalc.Core/Services/CostCalculators/Economy.cs: -------------------------------------------------------------------------------- 1 | namespace ParkCostCalc.Core.Services.CostCalculators 2 | { 3 | public class Economy : CalculatorBase, ICostCalc 4 | { 5 | private const decimal MAX_COST_PER_WEEK = 54; 6 | private const decimal MAX_COST_PER_DAY = 9; 7 | private const decimal MAX_COST_PER_HOUR = 2; 8 | 9 | public decimal CalculateCost(double totalMinutes) 10 | { 11 | return CalculateCost(totalMinutes, MAX_COST_PER_WEEK, MAX_COST_PER_DAY, MAX_COST_PER_HOUR); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/ParkCostCalc.Core/Services/CostCalculators/LongTermGarage.cs: -------------------------------------------------------------------------------- 1 | namespace ParkCostCalc.Core.Services.CostCalculators 2 | { 3 | public class LongTermGarage : CalculatorBase, ICostCalc 4 | { 5 | private const int MAX_COST_PER_WEEK = 72; 6 | private const int MAX_COST_PER_DAY = 12; 7 | private const int MAX_COST_PER_HOUR = 2; 8 | 9 | public decimal CalculateCost(double totalMinutes) 10 | { 11 | return CalculateCost(totalMinutes, MAX_COST_PER_WEEK, MAX_COST_PER_DAY, MAX_COST_PER_HOUR); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/ParkCostCalc.Core/Services/CostCalculators/LongTermSurface.cs: -------------------------------------------------------------------------------- 1 | namespace ParkCostCalc.Core.Services.CostCalculators 2 | { 3 | public class LongTermSurface : CalculatorBase, ICostCalc 4 | { 5 | private const int MAX_COST_PER_WEEK = 60; 6 | private const int MAX_COST_PER_DAY = 10; 7 | private const int MAX_COST_PER_HOUR = 2; 8 | 9 | public decimal CalculateCost(double totalMinutes) 10 | { 11 | return CalculateCost(totalMinutes, MAX_COST_PER_WEEK, MAX_COST_PER_DAY, MAX_COST_PER_HOUR); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/ParkCostCalc.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace ParkCostCalc.Api 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) 14 | { 15 | return Host.CreateDefaultBuilder(args) 16 | .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/Dsl/CostCalculatorDsl.cs: -------------------------------------------------------------------------------- 1 | using ParkCostCalc.AcceptanceTests.Drivers.CostCalculator; 2 | using ParkCostCalc.AcceptanceTests.Models; 3 | 4 | namespace ParkCostCalc.AcceptanceTests.Dsl 5 | { 6 | public class CostCalculatorDsl 7 | { 8 | private readonly ICostCalculatorDriver _driver; 9 | 10 | protected CostCalculatorDsl(ICostCalculatorDriver driver) 11 | { 12 | _driver = driver; 13 | } 14 | 15 | public decimal CalculateCost(ParkTypeEnum parkingLot, string duration) 16 | { 17 | return _driver.CalculateCost(parkingLot, duration); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/ParkCostCalc.Core/Services/CostCalculators/CalculatorFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace ParkCostCalc.Core.Services.CostCalculators 5 | { 6 | public static class CalculatorFactory 7 | { 8 | public static T Get(string name) where T : class 9 | { 10 | return typeof(T) 11 | .Assembly 12 | .GetTypes() 13 | .Where(type => type.GetInterfaces().Contains(typeof(T))) 14 | .Where(type => type.Name.Equals(name)) 15 | .Select(type => Activator.CreateInstance(type) as T) 16 | .SingleOrDefault(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/Features/ShortTerm.feature: -------------------------------------------------------------------------------- 1 | @fast 2 | Feature: Short-Term Parking feature 3 | The parking lot calculator can calculate costs for ShortTerm Parking. 4 | 5 | Scenario Outline: Calculate Short-Term Parking Cost 6 | Given parking lot is ShortTerm 7 | And parking duration is 8 | When the cost estimate is calculated 9 | Then the parking cost should be 10 | 11 | Examples: 12 | | duration | cost | 13 | | 0 minute | 0.00€ | 14 | | 30 minutes | 2.00€ | 15 | | 1 hour | 2.00€ | 16 | | 3 hours 30 minutes | 7.00€ | 17 | | 12 hours 30 minutes | 24.00€ | 18 | | 1 day 30 minutes | 25.00€ | 19 | | 1 day 1 hour | 26.00€ | -------------------------------------------------------------------------------- /src/ParkCostCalc.Api/ParkCostCalc.Api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/Features/Valet.feature: -------------------------------------------------------------------------------- 1 | @fast 2 | Feature: Valet Parking feature 3 | The parking lot calculator can calculate costs for Valet Parking. 4 | 5 | Scenario Outline: Calculate Valet Parking Cost 6 | Given parking lot is Valet 7 | And parking duration is 8 | When the cost estimate is calculated 9 | Then the parking cost should be 10 | 11 | Examples: 12 | | duration | cost | 13 | | 0 minute | 0.00€ | 14 | | 30 minutes | 12.00€ | 15 | | 3 hours | 12.00€ | 16 | | 5 hours | 12.00€ | 17 | | 5 hours, 1 minute | 18.00€ | 18 | | 12 hours | 18.00€ | 19 | | 24 hours | 18.00€ | 20 | | 1 day, 1 minute | 36.00€ | 21 | | 3 days | 54.00€ | 22 | | 1 week | 126.00€ | -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/Support/Hooks.cs: -------------------------------------------------------------------------------- 1 | using BoDi; 2 | using TechTalk.SpecFlow; 3 | 4 | namespace ParkCostCalc.AcceptanceTests.Support 5 | { 6 | [Binding] 7 | public class Hooks 8 | { 9 | private readonly IObjectContainer _objectContainer; 10 | 11 | public Hooks(IObjectContainer objectContainer) 12 | { 13 | _objectContainer = objectContainer; 14 | } 15 | 16 | [BeforeScenario("db")] 17 | public void InitializeDataBaseDependencies() 18 | { 19 | // todo: setup data for acceptance tests 20 | } 21 | 22 | [BeforeScenario] 23 | public void InitializeDependencies() 24 | { 25 | DependencyRegister.RegisterDependencies(_objectContainer); 26 | } 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/ParkCostCalc.UnitTests/ParkCostCalc.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/ParkCostCalc.Core/Helpers/MinuteConvertor.cs: -------------------------------------------------------------------------------- 1 | namespace ParkCostCalc.Core.Helpers 2 | { 3 | public class MinuteConvertor 4 | { 5 | private const int MinutesPerHour = 60; 6 | private const int HoursPerDay = 24; 7 | 8 | public static int Hours(int hours) 9 | { 10 | return DaysAndHoursAndMinutes(0, hours, 0); 11 | } 12 | 13 | public static int Days(int days) 14 | { 15 | return DaysAndHoursAndMinutes(days, 0, 0); 16 | } 17 | 18 | public static int HoursAndMinutes(int hours, int minutes) 19 | { 20 | return DaysAndHoursAndMinutes(0, hours, minutes); 21 | } 22 | 23 | public static int DaysAndHoursAndMinutes(int days, int hours, int minutes) 24 | { 25 | return (days * HoursPerDay + hours) * MinutesPerHour + minutes; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/Features/Economy.feature: -------------------------------------------------------------------------------- 1 | @fast 2 | Feature: Economy Parking feature 3 | The parking lot calculator can calculate costs for Economy parking. 4 | 5 | Scenario Outline: Calculate Economy Parking Cost 6 | Given parking lot is Economy 7 | And parking duration is 8 | When the cost estimate is calculated 9 | Then the parking cost should be 10 | 11 | Examples: 12 | | duration | cost | 13 | | 0 minute | 0.00€ | 14 | | 30 minutes | 2.00€ | 15 | | 1 hour | 2.00€ | 16 | | 4 hours | 8.00€ | 17 | | 5 hours | 9.00€ | 18 | | 6 hours | 9.00€ | 19 | | 24 hours | 9.00€ | 20 | | 1 day, 1 hour | 11.00€ | 21 | | 1 day, 3 hours | 15.00€ | 22 | | 1 day, 5 hours | 18.00€ | 23 | | 6 days | 54.00€ | 24 | | 6 days, 1 hour | 54.00€ | 25 | | 7 days | 54.00€ | 26 | | 1 week, 2 days | 72.00€ | 27 | | 3 weeks | 162.00€ | -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/Features/LongTermSurface.feature: -------------------------------------------------------------------------------- 1 | @fast 2 | Feature: Long-Term Surface Parking feature 3 | The parking lot calculator can calculate costs for Long-Term Surface parking. 4 | 5 | Scenario Outline: Calculate LongTermSurface Parking Cost 6 | Given parking lot is LongTermSurface 7 | And parking duration is 8 | When the cost estimate is calculated 9 | Then the parking cost should be 10 | 11 | Examples: 12 | | duration | cost | 13 | | 0 minute | 0.00€ | 14 | | 30 minutes | 2.00€ | 15 | | 1 hour | 2.00€ | 16 | | 5 hours | 10.00€ | 17 | | 6 hours | 10.00€ | 18 | | 24 hours | 10.00€ | 19 | | 1 day, 1 hour | 12.00€ | 20 | | 1 day, 3 hours | 16.00€ | 21 | | 1 day, 6 hours | 20.00€ | 22 | | 6 days | 60.00€ | 23 | | 6 days, 1 hour | 60.00€ | 24 | | 7 days | 60.00€ | 25 | | 1 week, 2 days | 80.00€ | 26 | | 3 weeks | 180.00€ | -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/Features/LongTermGarage.feature: -------------------------------------------------------------------------------- 1 | @fast 2 | Feature: Long-Term Garage Parking feature 3 | The parking lot calculator can calculate costs for Long-Term Garage parking. 4 | 5 | Scenario Outline: Calculate LongTermGarage Parking Cost 6 | Given parking lot is LongTermGarage 7 | And parking duration is 8 | When the cost estimate is calculated 9 | Then the parking cost should be 10 | 11 | Examples: 12 | | duration | cost | 13 | | 0 minute | 0.00€ | 14 | | 30 minutes | 2.00€ | 15 | | 1 hour | 2.00€ | 16 | | 3 hours | 6.00€ | 17 | | 6 hours | 12.00€ | 18 | | 7 hours | 12.00€ | 19 | | 24 hours | 12.00€ | 20 | | 1 day, 1 hour | 14.00€ | 21 | | 1 day, 3 hours | 18.00€ | 22 | | 1 day, 7 hours | 24.00€ | 23 | | 6 days | 72.00€ | 24 | | 6 days, 1 hour | 72.00€ | 25 | | 7 days | 72.00€ | 26 | | 1 week, 2 days | 96.00€ | 27 | | 3 weeks | 216.00€ | -------------------------------------------------------------------------------- /src/ParkCostCalc.Api/Controllers/CostCalculatorController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using ParkCostCalc.Core.Interfaces; 3 | using ParkCostCalc.Core.Models; 4 | using ParkCostCalc.Core.Services; 5 | 6 | namespace ParkCostCalc.Api.Controllers 7 | { 8 | [ApiController] 9 | [Route("[controller]")] 10 | public class CostCalculatorController : ControllerBase 11 | { 12 | private readonly IParkCostCalcService _costService; 13 | 14 | public CostCalculatorController(IParkCostCalcService costService) 15 | { 16 | _costService = costService; 17 | } 18 | 19 | [HttpPost] 20 | public IActionResult GetCost([FromBody] ParkRequest parkRequest) 21 | { 22 | if (!ModelState.IsValid) return BadRequest(); 23 | if ((parkRequest.ExitDate - parkRequest.EntryDate).Value.TotalMinutes < 0) 24 | return BadRequest("Entry date time cannot be less than exit date time!"); 25 | var costDetails = _costService.CalculateCost(parkRequest); 26 | return Ok(costDetails); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/ParkCostCalc.Core/Services/CostCalculators/Valet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ParkCostCalc.Core.Helpers; 3 | 4 | namespace ParkCostCalc.Core.Services.CostCalculators 5 | { 6 | public class Valet : CalculatorBase, ICostCalc 7 | { 8 | private const decimal COST_IN_5_HOURS = 12; 9 | private const decimal COST_PER_DAY = 18; 10 | 11 | public decimal CalculateCost(double totalMinutes) 12 | { 13 | var duration = TimeSpan.FromMinutes(totalMinutes); 14 | decimal totalCost = 0; 15 | if (totalMinutes <= 0) 16 | { 17 | totalCost = 0; 18 | } 19 | else if (totalMinutes <= MinuteConvertor.Hours(5)) 20 | { 21 | totalCost = COST_IN_5_HOURS; 22 | } 23 | else 24 | { 25 | var days = duration.Days; 26 | var startedDay = (duration.Hours | duration.Minutes) != 0 ? 1 : 0; 27 | var totalDays = days + startedDay; 28 | totalCost = totalDays * COST_PER_DAY; 29 | } 30 | 31 | return totalCost; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/ParkCostCalc.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:10147", 8 | "sslPort": 44356 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "ParkCostCalc.Api": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "swagger", 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | }, 28 | "DockerParkCostCalc.Api": { 29 | "commandName": "Docker", 30 | "launchBrowser": false, 31 | "useSSL": true, 32 | "launchUrl": "swagger", 33 | "applicationUrl": "https://webpark-api.herokuapp.com", 34 | "environmentVariables": { 35 | "ASPNETCORE_ENVIRONMENT": "Production" 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/ParkCostCalc.UnitTests/CostCalculators/ShortTermTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using ParkCostCalc.Core.Services.CostCalculators; 3 | 4 | namespace ParkCostCalc.UnitTests.CostCalculators 5 | { 6 | [TestFixture] 7 | public class ShortTermTest 8 | { 9 | private ICostCalc _costCalculator; 10 | 11 | [SetUp] 12 | public void Setup() 13 | { 14 | _costCalculator = new ShortTerm(); 15 | } 16 | 17 | [TestCase(-1, 0, TestName = "Cost for negative parking time")] 18 | [TestCase(0, 0, TestName = "Cost for 0 Minute")] 19 | [TestCase(30, 2, TestName = "Cost for 30 Minutes")] 20 | [TestCase((1 * 60), 2, TestName = "Cost for 1 Hour")] 21 | [TestCase((3 * 60) + 30, 7, TestName = "Cost for 3 Hours 30 Minutes")] 22 | [TestCase((12 * 60) + 30, 24, TestName = "Cost for 12 Hours 30 Minutes")] 23 | [TestCase((1 * 24 * 60) + 30, 25, TestName = "Cost for 1 Day 30 Minutes")] 24 | [TestCase((1 * 24 * 60) + (1 * 60), 26, TestName = "Cost for 1 Day 1 Hour")] 25 | public void Should_Charge_Expected_Cost(int totalMinutes, decimal expectedCost) 26 | { 27 | var cost = _costCalculator.CalculateCost(totalMinutes); 28 | 29 | Assert.AreEqual(expectedCost, cost); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/ParkCostCalc.Core/Services/ParkCostCalcService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ParkCostCalc.Core.Interfaces; 3 | using ParkCostCalc.Core.Models; 4 | using ParkCostCalc.Core.Services.CostCalculators; 5 | 6 | namespace ParkCostCalc.Core.Services 7 | { 8 | public class ParkCostCalcService : IParkCostCalcService 9 | { 10 | /// 11 | /// Calculate the parking cost 12 | /// 13 | /// Request represent the parking lot (type) and parking duration 14 | /// 15 | public CostDetails CalculateCost(ParkRequest parkRequest) 16 | { 17 | var costCalculator = CalculatorFactory.Get(parkRequest.ParkType.ToString()); 18 | if (costCalculator == null) return null; 19 | 20 | var totalMinutes = (parkRequest.ExitDate - parkRequest.EntryDate).Value.TotalMinutes; 21 | var totalCost = costCalculator.CalculateCost(totalMinutes); 22 | 23 | var duration = TimeSpan.FromMinutes(totalMinutes); 24 | 25 | return new CostDetails 26 | { 27 | Cost = decimal.Round(totalCost, 2), 28 | Days = duration.Days, 29 | Hours = duration.Hours, 30 | Minutes = duration.Minutes 31 | }; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/ParkCostCalc.Core/Services/CostCalculators/CalculatorBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ParkCostCalc.Core.Services.CostCalculators 4 | { 5 | public class CalculatorBase 6 | { 7 | protected decimal CalculateCost(double totalMinutes, decimal costsPerWeek, decimal costsPerDay, 8 | decimal costsPerHour) 9 | { 10 | decimal totalCost = 0; 11 | var duration = TimeSpan.FromMinutes(totalMinutes); 12 | if (totalMinutes <= 0) 13 | { 14 | totalCost = 0; 15 | } 16 | else 17 | { 18 | var totalWeeks = duration.Days / 7; 19 | var totalDays = duration.Days % 7; 20 | 21 | var startedHour = duration.Minutes > 0 ? 1 : 0; 22 | 23 | var totalHours = duration.Hours + startedHour; 24 | 25 | var weeksCost = totalWeeks * costsPerWeek; 26 | 27 | var daysCost = totalDays * costsPerDay; 28 | 29 | // 23h * 2 = 46 or max per day = 9 30 | var hoursCost = Math.Min(totalHours * costsPerHour, costsPerDay); 31 | 32 | var daysAndHoursCost = Math.Min(daysCost + hoursCost, costsPerWeek); 33 | 34 | totalCost = weeksCost + daysAndHoursCost; 35 | } 36 | 37 | return totalCost; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/ParkCostCalc.Core/Services/CostCalculators/ShortTerm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ParkCostCalc.Core.Services.CostCalculators 4 | { 5 | public class ShortTerm : CalculatorBase, ICostCalc 6 | { 7 | private const decimal COST_FIRST_HOUR = 2; 8 | private const decimal MAX_COST_PER_DAY = 24; 9 | private const decimal MAX_COST_PER_HALF_HOUR = 1; 10 | private const int ONE_MINUTE = 1; 11 | private const int HALF_HOUR = ONE_MINUTE * 30; 12 | private const int ONE_HOUR = ONE_MINUTE * 60; 13 | 14 | public decimal CalculateCost(double totalMinutes) 15 | { 16 | decimal totalCost = 0; 17 | var duration = TimeSpan.FromMinutes(totalMinutes); 18 | if (totalMinutes <= 0) 19 | { 20 | totalCost = 0; 21 | } 22 | else if (totalMinutes <= ONE_HOUR) 23 | { 24 | totalCost = COST_FIRST_HOUR; 25 | } 26 | else 27 | { 28 | var daysCost = duration.Days * MAX_COST_PER_DAY; 29 | 30 | var totalHalfHours = duration.Hours * 2 + duration.Minutes / HALF_HOUR + duration.Minutes % HALF_HOUR; 31 | var halfHoursCost = Math.Min(totalHalfHours * MAX_COST_PER_HALF_HOUR, MAX_COST_PER_DAY); 32 | 33 | totalCost = daysCost + halfHoursCost; 34 | } 35 | 36 | return totalCost; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /.github/workflows/ci-cd.yaml: -------------------------------------------------------------------------------- 1 | name: commit-and-acceptance-stages 2 | 3 | on: [push, workflow_dispatch] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Enable git longpaths 12 | run: git config --global core.longpaths true 13 | - name: Setup .NET Core SDK (v3.1.102) 14 | uses: actions/setup-dotnet@v1 15 | with: 16 | dotnet-version: 3.1.102 17 | - name: Checkout 18 | uses: actions/checkout@v2.0.0 19 | - name: Build solution 20 | run: | 21 | dotnet restore 22 | dotnet build 23 | - name: Run UnitTests (ParkCostCalc.Core.UnitTests) 24 | run: | 25 | dotnet test tests/ParkCostCalc.UnitTests -v m 26 | 27 | - name: Create RC & Deploy 28 | run: | 29 | echo "RC" 30 | echo "Deploy RC" 31 | 32 | - name: Run AcceptanceTests 33 | run: | 34 | dotnet test tests/ParkCostCalc.AcceptanceTests -v m 35 | 36 | - name: Installing SpecFlow.Plus.LivingDoc.CLI 37 | run: | 38 | dotnet tool install --global SpecFlow.Plus.LivingDoc.CLI 39 | 40 | - name: Living-Doc 41 | run: | 42 | livingdoc feature-data tests/ParkCostCalc.AcceptanceTests/bin/Debug/netcoreapp3.1/FeatureData.json 43 | 44 | - name: Upload report 45 | uses: actions/upload-artifact@v2 46 | with: 47 | name: ParkCostCalc 48 | path: ./LivingDoc.html -------------------------------------------------------------------------------- /tests/ParkCostCalc.UnitTests/CostCalculators/ValetTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using ParkCostCalc.Core.Services.CostCalculators; 3 | 4 | namespace ParkCostCalc.UnitTests.CostCalculators 5 | { 6 | [TestFixture] 7 | public class ValetTest 8 | { 9 | private ICostCalc _costCalculator; 10 | 11 | [SetUp] 12 | public void Setup() 13 | { 14 | _costCalculator = new Valet(); 15 | } 16 | 17 | [TestCase(-1, 0, TestName = "Cost for negative parking time")] 18 | [TestCase(0, 0, TestName = "Cost for 0 Minute")] 19 | [TestCase(30, 12, TestName = "Cost for 30 Minutes")] 20 | [TestCase((3 * 60), 12, TestName = "Cost for 3 Hours")] 21 | [TestCase((5 * 60), 12, TestName = "Cost for 5 Hours")] 22 | [TestCase((5 * 60) + 1, 18, TestName = "Cost for 5 Hours 1 Minute")] 23 | [TestCase((12 * 60), 18, TestName = "Cost for 12 Hours")] 24 | [TestCase((12 * 60), 18, TestName = "Cost for 24 Hours")] 25 | [TestCase((1 * 24 * 60) + 1, 36, TestName = "Cost for 1 Day 1 Minute")] 26 | [TestCase((3 * 24 * 60), 54, TestName = "Cost for 3 Days")] 27 | [TestCase((1 * 7 * 24 * 60), 126, TestName = "Cost for 1 Week")] 28 | public void Should_Charge_Expected_Cost(int totalMinutes, decimal expectedCost) 29 | { 30 | var cost = _costCalculator.CalculateCost(totalMinutes); 31 | 32 | Assert.AreEqual(expectedCost, cost); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/ParkCostCalc.AcceptanceTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Always 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/Drivers/CostCalculator/CostCalculatorApiDriver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ParkCostCalc.AcceptanceTests.Helpers; 3 | using ParkCostCalc.AcceptanceTests.Models; 4 | using RestSharp; 5 | using RestSharp.Serialization.Json; 6 | 7 | namespace ParkCostCalc.AcceptanceTests.Drivers.CostCalculator 8 | { 9 | public class CostCalculatorApiDriver : ICostCalculatorDriver 10 | { 11 | public decimal CalculateCost(ParkTypeEnum parkingType, string duration) 12 | { 13 | var apiBaseUrl = "https://webpark-api.herokuapp.com"; 14 | var requestUrl = "/CostCalculator"; 15 | 16 | var totalMinutes = Parser.ParseDuration(duration); 17 | DateTime entryDate = DateTime.Now; 18 | DateTime exitDate = entryDate.AddMinutes(totalMinutes); 19 | 20 | var requestData = new 21 | { 22 | parkType = parkingType.ToString(), 23 | entryDate = entryDate, 24 | exitDate = exitDate 25 | }; 26 | 27 | var restClient = new RestClient(apiBaseUrl); 28 | var request = new RestRequest(requestUrl, Method.POST); 29 | request.RequestFormat = RestSharp.DataFormat.Json; 30 | request.AddJsonBody(requestData); 31 | request.Timeout = 600000; 32 | var response = restClient.Execute(request); 33 | CostResponse costResponse = new JsonDeserializer().Deserialize(response); 34 | return Decimal.Parse(costResponse.Cost); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /tests/ParkCostCalc.UnitTests/CostCalculators/LongTermSurfaceTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using ParkCostCalc.Core.Services.CostCalculators; 3 | 4 | namespace ParkCostCalc.UnitTests.CostCalculators 5 | { 6 | [TestFixture] 7 | public class LongTermSurfaceTest 8 | { 9 | private ICostCalc _costCalculator; 10 | 11 | [SetUp] 12 | public void Setup() 13 | { 14 | _costCalculator = new LongTermSurface(); 15 | } 16 | 17 | [TestCase(-1, 0, TestName = "Cost for negative parking time")] 18 | [TestCase(0, 0, TestName = "Cost for 0 Minute")] 19 | [TestCase(30, 2, TestName = "Cost for 30 Minutes")] 20 | [TestCase((1 * 60), 2, TestName = "Cost for 1 Hour")] 21 | [TestCase((5 * 60), 10, TestName = "Cost for 5 Hours")] 22 | [TestCase((6 * 60), 10, TestName = "Cost for 6 Hours")] 23 | [TestCase((24 * 60), 10, TestName = "Cost for 24 Hours")] 24 | [TestCase((1 * 24 * 60) + (1 * 60), 12, TestName = "Cost for 1 Day 1 Hour")] 25 | [TestCase((1 * 24 * 60) + (3 * 60), 16, TestName = "Cost for 1 Day 3 Hours")] 26 | [TestCase((1 * 24 * 60) + (6 * 60), 20, TestName = "Cost for 1 Day 6 Hours")] 27 | [TestCase((6 * 24 * 60), 60, TestName = "Cost for 6 Days")] 28 | [TestCase((6 * 24 * 60) + (1 * 60), 60, TestName = "Cost for 6 Days 1 Hour")] 29 | [TestCase((7 * 24 * 60), 60, TestName = "Cost for 7 Days")] 30 | [TestCase((1 * 7 * 24 * 60) + (2 * 24 * 60), 80, TestName = "Cost for 1 Week 2 Days")] 31 | [TestCase((3 * 7 * 24 * 60), 180, TestName = "Cost for 3 Weeks")] 32 | public void Should_Charge_Expected_Cost(int totalMinutes, decimal expectedCost) 33 | { 34 | var cost = _costCalculator.CalculateCost(totalMinutes); 35 | 36 | Assert.AreEqual(expectedCost, cost); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /tests/ParkCostCalc.UnitTests/CostCalculators/EconomyTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using ParkCostCalc.Core.Services.CostCalculators; 3 | 4 | namespace ParkCostCalc.UnitTests.CostCalculators 5 | { 6 | [TestFixture] 7 | public class EconomyTest 8 | { 9 | private ICostCalc _costCalculator; 10 | 11 | [SetUp] 12 | public void Setup() 13 | { 14 | _costCalculator = new Economy(); 15 | } 16 | 17 | [TestCase(-1, 0, TestName = "Cost for negative parking time")] 18 | [TestCase(0, 0, TestName = "Cost for 0 Minute")] 19 | [TestCase(30, 2, TestName = "Cost for 30 Minutes")] 20 | [TestCase((1 * 60), 2, TestName = "Cost for 1 Hour")] 21 | [TestCase((4 * 60), 8, TestName = "Cost for 4 Hours")] 22 | [TestCase((5 * 60), 9, TestName = "Cost for 5 Hours")] 23 | [TestCase((6 * 60), 9, TestName = "Cost for 6 Hours")] 24 | [TestCase((24 * 60), 9, TestName = "Cost for 24 Hours")] 25 | [TestCase((1 * 24 * 60) + (1 * 60), 11, TestName = "Cost for 1 Day 1 Hour")] 26 | [TestCase((1 * 24 * 60) + (3 * 60), 15, TestName = "Cost for 1 Day 3 Hours")] 27 | [TestCase((1 * 24 * 60) + (5 * 60), 18, TestName = "Cost for 1 Day 5 Hours")] 28 | [TestCase((6 * 24 * 60), 54, TestName = "Cost for 6 Days")] 29 | [TestCase((6 * 24 * 60) + (1 * 60), 54, TestName = "Cost for 6 Days 1 Hour")] 30 | [TestCase((7 * 24 * 60), 54, TestName = "Cost for 7 Days")] 31 | [TestCase((1 * 7 * 24 * 60) + (2 * 24 * 60), 72, TestName = "Cost for 1 Week 2 Days")] 32 | [TestCase((3 * 7 * 24 * 60), 162, TestName = "Cost for 3 Weeks")] 33 | public void Should_Charge_Expected_Cost(int totalMinutes, decimal expectedCost) 34 | { 35 | var cost = _costCalculator.CalculateCost(totalMinutes); 36 | Assert.AreEqual(expectedCost, cost); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/StepDefinitions/CostCalculatorSteps.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using ParkCostCalc.AcceptanceTests.Dsl; 3 | using ParkCostCalc.AcceptanceTests.Helpers; 4 | using ParkCostCalc.AcceptanceTests.Models; 5 | using TechTalk.SpecFlow; 6 | 7 | namespace ParkCostCalc.AcceptanceTests.StepDefinitions 8 | { 9 | [Binding] 10 | public class CostCalculatorSteps 11 | { 12 | private readonly ScenarioContext _scenarioContext; 13 | private readonly CostCalculatorDsl _costCalcDsl; 14 | 15 | public CostCalculatorSteps(ScenarioContext scenarioContext, CostCalculatorDsl costCalculatorDsl) 16 | { 17 | _scenarioContext = scenarioContext; 18 | _costCalcDsl = costCalculatorDsl; 19 | } 20 | 21 | [Given(@"parking lot is (.*)")] 22 | public void GivenParkingLotIs(ParkTypeEnum parkingLot) 23 | { 24 | _scenarioContext.Add("parkingLot", parkingLot); 25 | } 26 | 27 | [Given(@"parking duration is (.*)")] 28 | public void GivenParkingDuration(string duration) 29 | { 30 | _scenarioContext.Add("duration", duration); 31 | } 32 | 33 | [When(@"the cost estimate is calculated")] 34 | public void WhenTheCostEstimateIsCalculated() 35 | { 36 | _scenarioContext.TryGetValue("duration", out string duration); 37 | _scenarioContext.TryGetValue("parkingLot", out ParkTypeEnum parkType); 38 | 39 | var cost = _costCalcDsl.CalculateCost(parkType, duration); 40 | _scenarioContext.Add("cost", cost); 41 | } 42 | 43 | [Then(@"the parking cost should be (.*)")] 44 | public void ThenTheParkingCostShouldBe(string expectedCost) 45 | { 46 | _scenarioContext.TryGetValue("cost", out decimal cost); 47 | Assert.AreEqual(Parser.ParseCost(expectedCost), cost); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /tests/ParkCostCalc.UnitTests/CostCalculators/LongTermGarageTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using ParkCostCalc.Core.Services.CostCalculators; 3 | 4 | namespace ParkCostCalc.UnitTests.CostCalculators 5 | { 6 | [TestFixture] 7 | public class LongTermGarageTest 8 | { 9 | private ICostCalc _costCalculator; 10 | 11 | [SetUp] 12 | public void Setup() 13 | { 14 | _costCalculator = new LongTermGarage(); 15 | } 16 | 17 | [TestCase(-1, 0, TestName = "Cost for negative parking time")] 18 | [TestCase(0, 0, TestName = "Cost for 0 Minute")] 19 | [TestCase(30, 2, TestName = "Cost for 30 Minutes")] 20 | [TestCase((1 * 60), 2, TestName = "Cost for 1 Hour")] 21 | [TestCase((3 * 60), 6, TestName = "Cost for 3 Hours")] 22 | [TestCase((6 * 60), 12, TestName = "Cost for 6 Hours")] 23 | [TestCase((7 * 60), 12, TestName = "Cost for 7 Hours")] 24 | [TestCase((24 * 60), 12, TestName = "Cost for 24 Hours")] 25 | [TestCase((1 * 24 * 60) + (1 * 60), 14, TestName = "Cost for 1 Day 1 Hour")] 26 | [TestCase((1 * 24 * 60) + (3 * 60), 18, TestName = "Cost for 1 Day 3 Hours")] 27 | [TestCase((1 * 24 * 60) + (7 * 60), 24, TestName = "Cost for 1 Day 7 Hours")] 28 | [TestCase((6 * 24 * 60), 72, TestName = "Cost for 6 Days")] 29 | [TestCase((6 * 24 * 60) + (1 * 60), 72, TestName = "Cost for 6 Days 1 Hour")] 30 | [TestCase((7 * 24 * 60), 72, TestName = "Cost for 7 Days")] 31 | [TestCase((1 * 7 * 24 * 60) + (2 * 24 * 60), 96, TestName = "Cost for 1 Week 2 Days")] 32 | [TestCase((3 * 7 * 24 * 60), 216, TestName = "Cost for 3 Weeks")] 33 | public void Should_Charge_Expected_Cost(int totalMinutes, decimal expectedCost) 34 | { 35 | var cost = _costCalculator.CalculateCost(totalMinutes); 36 | 37 | Assert.AreEqual(expectedCost, cost); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /src/ParkCostCalc.Api/Startup.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Hosting; 7 | using Microsoft.OpenApi.Models; 8 | using ParkCostCalc.Core.Interfaces; 9 | using ParkCostCalc.Core.Services; 10 | 11 | namespace ParkCostCalc.Api 12 | { 13 | public class Startup 14 | { 15 | public Startup(IConfiguration configuration) 16 | { 17 | Configuration = configuration; 18 | } 19 | 20 | public IConfiguration Configuration { get; } 21 | 22 | // This method gets called by the runtime. Use this method to add services to the container. 23 | public void ConfigureServices(IServiceCollection services) 24 | { 25 | services.AddControllers().AddJsonOptions(opts => 26 | { 27 | opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); 28 | }); 29 | 30 | // Services 31 | services.AddTransient(); 32 | 33 | //Update as appropriate for origin, method, header 34 | services.AddCors(options => 35 | { 36 | options.AddPolicy("AllowAnyOrigin", 37 | builder => builder 38 | .AllowAnyOrigin() 39 | .AllowAnyMethod() 40 | .AllowAnyHeader() 41 | ); 42 | }); 43 | 44 | services.AddSwaggerGen(c => 45 | { 46 | c.SwaggerDoc("v1", new OpenApiInfo {Title = "WebPark API by Tawfik NOURI", Version = "v1"}); 47 | }); 48 | } 49 | 50 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 51 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 52 | { 53 | if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); 54 | 55 | app.UseHttpsRedirection(); 56 | 57 | app.UseRouting(); 58 | 59 | app.UseAuthorization(); 60 | 61 | app.UseCors("AllowAnyOrigin"); 62 | 63 | app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); 64 | 65 | // Enable middleware to serve generated Swagger as a JSON endpoint 66 | app.UseSwagger(); 67 | 68 | // Enable middleware to serve swagger-ui assets (HTML, JS, CSS etc.) 69 | // Visit http://localhost:5000/swagger 70 | app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "Parking Cost Calculator API V1"); }); 71 | 72 | app.UseStatusCodePagesWithRedirects("/swagger/index.html"); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /tests/ParkCostCalc.AcceptanceTests/Helpers/Parser.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace ParkCostCalc.AcceptanceTests.Helpers 4 | { 5 | public static class Parser 6 | { 7 | private const int MinutesPerHour = 60; 8 | private const int MinutesPerDay = 24 * MinutesPerHour; 9 | private const int MinutesPerWeek = 7 * MinutesPerDay; 10 | 11 | public static int ParseDuration(string duration) 12 | { 13 | int totalMinutes = 0; 14 | 15 | totalMinutes += ParseWeeks(duration) * MinutesPerWeek; 16 | totalMinutes += ParseDays(duration) * MinutesPerDay; 17 | totalMinutes += ParseHours(duration) * MinutesPerHour; 18 | totalMinutes += ParseMinutes(duration); 19 | 20 | return totalMinutes; 21 | } 22 | 23 | public static decimal ParseCost(string duration) 24 | { 25 | string dayPattern = @"(\d+)"; 26 | Match match = Regex.Match(duration, dayPattern); 27 | if (match.Success) 28 | { 29 | string minText = match.Groups[1].Value; 30 | return decimal.Parse(minText); 31 | } 32 | 33 | return 0; 34 | } 35 | 36 | 37 | private static int ParseWeeks(string duration) 38 | { 39 | return ParseNumberAccordingToPattern(@"(\d+)\s?week(s)?", duration); 40 | // return ParseNumberAccordingToPattern(@"(\d+)\s?(week(s)|semaine(s))?", duration); 41 | } 42 | 43 | private static int ParseDays(string duration) 44 | { 45 | return ParseNumberAccordingToPattern(@"(\d+)\s?day(s)?", duration); 46 | } 47 | 48 | private static int ParseHours(string duration) 49 | { 50 | Match match = Regex.Match(duration, @"(\d+)\s?hour(s)?"); 51 | if (match.Success) 52 | { 53 | string minText = match.Groups[1].Value; 54 | return int.Parse(minText); 55 | } 56 | 57 | return 0; 58 | } 59 | 60 | private static int ParseMinutes(string duration) 61 | { 62 | Match match = Regex.Match(duration, @"(\d+)\s?minute(s)?"); 63 | if (match.Success) 64 | { 65 | string minText = match.Groups[1].Value; 66 | return int.Parse(minText); 67 | } 68 | 69 | return 0; 70 | } 71 | 72 | private static int ParseNumberAccordingToPattern(string dayPattern, string duration) 73 | { 74 | Match match = Regex.Match(duration, dayPattern); 75 | if (match.Success) 76 | { 77 | string minText = match.Groups[1].Value; 78 | return int.Parse(minText); 79 | } 80 | 81 | return 0; 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /ParkingCostCalculator.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29806.167 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParkCostCalc.Core", "src\ParkCostCalc.Core\ParkCostCalc.Core.csproj", "{07375135-2088-46CD-92BD-52324239FAE3}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParkCostCalc.Api", "src\ParkCostCalc.Api\ParkCostCalc.Api.csproj", "{E081E434-4337-4BA5-BCD2-7344C0EB12D6}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F0A54108-8349-414C-AB99-4F95115BBC04}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A38FD08F-DCEC-4676-9627-5AE723A699A6}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParkCostCalc.AcceptanceTests", "tests\ParkCostCalc.AcceptanceTests\ParkCostCalc.AcceptanceTests.csproj", "{6BB3EA4F-9F89-4E37-A1BD-CD0D7529F013}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParkCostCalc.UnitTests", "tests\ParkCostCalc.UnitTests\ParkCostCalc.UnitTests.csproj", "{8193D81F-31F1-439A-80A8-9A969F3A0704}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {07375135-2088-46CD-92BD-52324239FAE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {07375135-2088-46CD-92BD-52324239FAE3}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {07375135-2088-46CD-92BD-52324239FAE3}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {07375135-2088-46CD-92BD-52324239FAE3}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {E081E434-4337-4BA5-BCD2-7344C0EB12D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {E081E434-4337-4BA5-BCD2-7344C0EB12D6}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {E081E434-4337-4BA5-BCD2-7344C0EB12D6}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {E081E434-4337-4BA5-BCD2-7344C0EB12D6}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {6BB3EA4F-9F89-4E37-A1BD-CD0D7529F013}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {6BB3EA4F-9F89-4E37-A1BD-CD0D7529F013}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {6BB3EA4F-9F89-4E37-A1BD-CD0D7529F013}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {6BB3EA4F-9F89-4E37-A1BD-CD0D7529F013}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {8193D81F-31F1-439A-80A8-9A969F3A0704}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {8193D81F-31F1-439A-80A8-9A969F3A0704}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {8193D81F-31F1-439A-80A8-9A969F3A0704}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {8193D81F-31F1-439A-80A8-9A969F3A0704}.Release|Any CPU.Build.0 = Release|Any CPU 40 | EndGlobalSection 41 | GlobalSection(SolutionProperties) = preSolution 42 | HideSolutionNode = FALSE 43 | EndGlobalSection 44 | GlobalSection(NestedProjects) = preSolution 45 | {07375135-2088-46CD-92BD-52324239FAE3} = {A38FD08F-DCEC-4676-9627-5AE723A699A6} 46 | {E081E434-4337-4BA5-BCD2-7344C0EB12D6} = {A38FD08F-DCEC-4676-9627-5AE723A699A6} 47 | {6BB3EA4F-9F89-4E37-A1BD-CD0D7529F013} = {F0A54108-8349-414C-AB99-4F95115BBC04} 48 | {8193D81F-31F1-439A-80A8-9A969F3A0704} = {F0A54108-8349-414C-AB99-4F95115BBC04} 49 | EndGlobalSection 50 | GlobalSection(ExtensibilityGlobals) = postSolution 51 | SolutionGuid = {925F0B4E-D04A-4F10-9F43-E68302BEBA7D} 52 | EndGlobalSection 53 | EndGlobal 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BDD/TDD With SpecFlow & .NET Core 2 | 3 | [![Master Actions Status](https://github.com/tawfiknouri/BDD-TDD_ParkingCostCalculator_SpecFlow/workflows/commit-and-acceptance-stages/badge.svg)](https://github.com/tawfiknouri/BDD-TDD_ParkingCostCalculator_SpecFlow/actions) 4 | 5 | The intention of this project is not create the best parking cost calculator in the world. 6 | We want the simplest parking cost calculator in the world so we can focus on Behavior Driven Development (BDD) and Test Driven Development (TDD) practice 7 | 8 | The main goal is to share knowledge ! 9 | 10 | Please do not hesitate to contact me if you have any questions or PR. 11 | 12 | 13 | **The example also appears in the official specflow documentation.** 14 | 15 | See here for details: **https://docs.specflow.org/en/latest/Examples.html#community-sample-projects** 16 | 17 | 18 | Thanks for enjoying! 19 | 20 | 21 | # Requirements 22 | 23 | Imagine we were to design a parking cost calculator that calculates the price of parking tickets at the airport. There could be different parking sites like 24 | 25 | * Valet Parking 26 | * Short-Term Parking 27 | * Long-Term Garage Parking 28 | * Long-Term Surface Parking 29 | * Economy Lot Parking 30 | 31 | With each site having its own set of rules how a ticket price is calculated: 32 | ## Valet Parking 33 | - 18.00€ per day 34 | - 12.00€ for five hours or less 35 | ## Short-Term Parking 36 | - 2.00€ first hour; 1.00€ each additional 1/2 hour 37 | - 24.00€ daily maximum 38 | ## Long-Term Garage Parking 39 | - 2.00€ per hour 40 | - 2.00€ daily maximum 41 | - 72.00€ per week (7th day free) 42 | ## Long-Term Surface Parking 43 | - 2.00€ per hour 44 | - 10.00€ daily maximum 45 | - 60.00€ per week (7th day free) 46 | ## Economy Lot Parking 47 | - 2.00€ per hour 48 | - 9.00€ daily maximum 49 | - 54.00€ per week (7th day free) 50 | 51 | # The Technologies 52 | * [SpecFlow](https://specflow.org/) 53 | * [.NET Core](https://dotnet.microsoft.com/download) 54 | * [Entity Framework Core](https://docs.microsoft.com/en-us/ef/core) 55 | * [Nunit](https://nunit.org/) 56 | 57 | 58 | # BDD practices 59 | ## Discovery 60 | ## Formulation 61 | 62 | ### Valet Parking 63 | 64 | ``` gherkin 65 | @fast 66 | Feature: Valet Parking feature 67 | The parking lot calculator can calculate costs for Valet Parking. 68 | 69 | Scenario Outline: Calculate Valet Parking Cost 70 | Given parking lot is Valet 71 | And parking duration is 72 | When the cost estimate is calculated 73 | Then the parking cost should be 74 | 75 | Examples: 76 | | duration | cost | 77 | | 0 minute | 0.00€ | 78 | | 30 minutes | 12.00€ | 79 | | 3 hours | 12.00€ | 80 | | 5 hours | 12.00€ | 81 | | 5 hours, 1 minute | 18.00€ | 82 | | 12 hours | 18.00€ | 83 | | 24 hours | 18.00€ | 84 | | 1 day, 1 minute | 36.00€ | 85 | | 3 days | 54.00€ | 86 | | 1 week | 126.00€ | 87 | ``` 88 | 89 | ### Short-Term Parking 90 | 91 | ``` gherkin 92 | @fast 93 | Feature: Short-Term Parking feature 94 | The parking lot calculator can calculate costs for ShortTerm Parking. 95 | 96 | Scenario Outline: Calculate Short-Term Parking Cost 97 | Given parking lot is ShortTerm 98 | And parking duration is 99 | When the cost estimate is calculated 100 | Then the parking cost should be 101 | 102 | Examples: 103 | | duration | cost | 104 | | 0 minute | 0.00€ | 105 | | 30 minutes | 2.00€ | 106 | | 1 hour | 2.00€ | 107 | | 3 hours 30 minutes | 7.00€ | 108 | | 12 hours 30 minutes | 24.00€ | 109 | | 1 day 30 minutes | 25.00€ | 110 | | 1 day 1 hour | 26.00€ | 111 | ``` 112 | 113 | ### Long-Term Garage Parking 114 | 115 | ``` gherkin 116 | @fast 117 | Feature: Long-Term Garage Parking feature 118 | The parking lot calculator can calculate costs for Long-Term Garage parking. 119 | 120 | Scenario Outline: Calculate LongTermGarage Parking Cost 121 | Given parking lot is LongTermGarage 122 | And parking duration is 123 | When the cost estimate is calculated 124 | Then the parking cost should be 125 | 126 | Examples: 127 | | duration | cost | 128 | | 0 minute | 0.00€ | 129 | | 30 minutes | 2.00€ | 130 | | 1 hour | 2.00€ | 131 | | 3 hours | 6.00€ | 132 | | 6 hours | 12.00€ | 133 | | 7 hours | 12.00€ | 134 | | 24 hours | 12.00€ | 135 | | 1 day, 1 hour | 14.00€ | 136 | | 1 day, 3 hours | 18.00€ | 137 | | 1 day, 7 hours | 24.00€ | 138 | | 6 days | 72.00€ | 139 | | 6 days, 1 hour | 72.00€ | 140 | | 7 days | 72.00€ | 141 | | 1 week, 2 days | 96.00€ | 142 | | 3 weeks | 216.00€ | 143 | ``` 144 | 145 | 146 | ## Automation 147 | 148 | ### Living Documentation 149 | 150 | ![alt text](./docs/Images/Living_Documentation.png "Living Documentation") 151 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | 5 | # specflow feature files 6 | 7 | *.feature.cs 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | [Ll]og/ 28 | 29 | # Visual Studio 2015 cache/options directory 30 | .vs/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # MSTest test Results 35 | [Tt]est[Rr]esult*/ 36 | [Bb]uild[Ll]og.* 37 | 38 | # NUNIT 39 | *.VisualState.xml 40 | TestResult.xml 41 | 42 | # Build Results of an ATL Project 43 | [Dd]ebugPS/ 44 | [Rr]eleasePS/ 45 | dlldata.c 46 | 47 | # DNX 48 | project.lock.json 49 | project.fragment.lock.json 50 | artifacts/ 51 | 52 | *_i.c 53 | *_p.c 54 | *_i.h 55 | *.ilk 56 | *.meta 57 | *.obj 58 | *.pch 59 | *.pdb 60 | *.pgc 61 | *.pgd 62 | *.rsp 63 | *.sbr 64 | *.tlb 65 | *.tli 66 | *.tlh 67 | *.tmp 68 | *.tmp_proj 69 | *.log 70 | *.vspscc 71 | *.vssscc 72 | .builds 73 | *.pidb 74 | *.svclog 75 | *.scc 76 | 77 | # Chutzpah Test files 78 | _Chutzpah* 79 | 80 | # Visual C++ cache files 81 | ipch/ 82 | *.aps 83 | *.ncb 84 | *.opendb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | *.VC.db 89 | *.VC.VC.opendb 90 | 91 | # Visual Studio profiler 92 | *.psess 93 | *.vsp 94 | *.vspx 95 | *.sap 96 | 97 | # TFS 2012 Local Workspace 98 | $tf/ 99 | 100 | # Guidance Automation Toolkit 101 | *.gpState 102 | 103 | # ReSharper is a .NET coding add-in 104 | _ReSharper*/ 105 | *.[Rr]e[Ss]harper 106 | *.DotSettings.user 107 | 108 | # JustCode is a .NET coding add-in 109 | .JustCode 110 | 111 | # TeamCity is a build add-in 112 | _TeamCity* 113 | 114 | # DotCover is a Code Coverage Tool 115 | *.dotCover 116 | 117 | # NCrunch 118 | _NCrunch_* 119 | .*crunch*.local.xml 120 | nCrunchTemp_* 121 | 122 | # MightyMoose 123 | *.mm.* 124 | AutoTest.Net/ 125 | 126 | # Web workbench (sass) 127 | .sass-cache/ 128 | 129 | # Installshield output folder 130 | [Ee]xpress/ 131 | 132 | # DocProject is a documentation generator add-in 133 | DocProject/buildhelp/ 134 | DocProject/Help/*.HxT 135 | DocProject/Help/*.HxC 136 | DocProject/Help/*.hhc 137 | DocProject/Help/*.hhk 138 | DocProject/Help/*.hhp 139 | DocProject/Help/Html2 140 | DocProject/Help/html 141 | 142 | # Click-Once directory 143 | publish/ 144 | 145 | # Publish Web Output 146 | *.[Pp]ublish.xml 147 | *.azurePubxml 148 | # TODO: Comment the next line if you want to checkin your web deploy settings 149 | # but database connection strings (with potential passwords) will be unencrypted 150 | #*.pubxml 151 | *.publishproj 152 | 153 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 154 | # checkin your Azure Web App publish settings, but sensitive information contained 155 | # in these scripts will be unencrypted 156 | PublishScripts/ 157 | 158 | # NuGet Packages 159 | *.nupkg 160 | # The packages folder can be ignored because of Package Restore 161 | **/packages/* 162 | # except build/, which is used as an MSBuild target. 163 | !**/packages/build/ 164 | # Uncomment if necessary however generally it will be regenerated when needed 165 | #!**/packages/repositories.config 166 | # NuGet v3's project.json files produces more ignoreable files 167 | *.nuget.props 168 | *.nuget.targets 169 | 170 | # Microsoft Azure Build Output 171 | csx/ 172 | *.build.csdef 173 | 174 | # Microsoft Azure Emulator 175 | ecf/ 176 | rcf/ 177 | 178 | # Windows Store app package directories and files 179 | AppPackages/ 180 | BundleArtifacts/ 181 | Package.StoreAssociation.xml 182 | _pkginfo.txt 183 | 184 | # Visual Studio cache files 185 | # files ending in .cache can be ignored 186 | *.[Cc]ache 187 | # but keep track of directories ending in .cache 188 | !*.[Cc]ache/ 189 | 190 | # Others 191 | ClientBin/ 192 | ~$* 193 | *~ 194 | *.dbmdl 195 | *.dbproj.schemaview 196 | *.jfm 197 | *.pfx 198 | *.publishsettings 199 | node_modules/ 200 | orleans.codegen.cs 201 | 202 | # Since there are multiple workflows, uncomment next line to ignore bower_components 203 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 204 | #bower_components/ 205 | 206 | # RIA/Silverlight projects 207 | Generated_Code/ 208 | 209 | 210 | # Specflow reporting 211 | TestResults/ 212 | TestResults/.html 213 | TestResults/.log 214 | 215 | # Backup & report files from converting an old project file 216 | # to a newer Visual Studio version. Backup files are not needed, 217 | # because we have git ;-) 218 | _UpgradeReport_Files/ 219 | Backup*/ 220 | UpgradeLog*.XML 221 | UpgradeLog*.htm 222 | 223 | # SQL Server files 224 | *.mdf 225 | *.ldf 226 | 227 | # Business Intelligence projects 228 | *.rdl.data 229 | *.bim.layout 230 | *.bim_*.settings 231 | 232 | # Microsoft Fakes 233 | FakesAssemblies/ 234 | 235 | # GhostDoc plugin setting file 236 | *.GhostDoc.xml 237 | 238 | # Node.js Tools for Visual Studio 239 | .ntvs_analysis.dat 240 | 241 | # Visual Studio 6 build log 242 | *.plg 243 | 244 | # Visual Studio 6 workspace options file 245 | *.opt 246 | 247 | # Visual Studio LightSwitch build output 248 | **/*.HTMLClient/GeneratedArtifacts 249 | **/*.DesktopClient/GeneratedArtifacts 250 | **/*.DesktopClient/ModelManifest.xml 251 | **/*.Server/GeneratedArtifacts 252 | **/*.Server/ModelManifest.xml 253 | _Pvt_Extensions 254 | 255 | # Paket dependency manager 256 | .paket/paket.exe 257 | paket-files/ 258 | 259 | # FAKE - F# Make 260 | .fake/ 261 | 262 | # JetBrains Rider 263 | .idea/ 264 | *.sln.iml 265 | 266 | # CodeRush 267 | .cr/ 268 | 269 | # Python Tools for Visual Studio (PTVS) 270 | __pycache__/ 271 | *.pyc 272 | --------------------------------------------------------------------------------