├── .gitattributes ├── .gitignore ├── BREAKING_CHANGES.md ├── GitVersion.yml ├── appveyor.yml ├── azure-pipelines.yml ├── bddfy-report.png ├── build.cake ├── build.ps1 ├── docs ├── concepts-context-specification.md ├── concepts-system-under-test.md ├── consistent-reporting.png ├── features-overview.md ├── github.png ├── index.md ├── nuget-install-package-specify.png ├── nuget-manage-nuget-packages-specify.png ├── twitter.png ├── using-specify-configuration.md ├── using-specify-getting-started.md ├── using-specify-scenario-lifecycle.md ├── using-specify-supported-test-frameworks.md └── visualstudio_enablesourceserversupport.png ├── license.txt ├── mkdocs.yml ├── nuget ├── ScenarioFor.cs.pp ├── Specify.Autofac.nuspec ├── Specify.nuspec ├── SpecifyAutofacBootstrapper.cs.pp └── SpecifyBootstrapper.cs.pp ├── readme.md └── src ├── NuGet.config ├── Samples ├── AspNetCoreApi │ ├── AspNetCoreApi.sln │ ├── app │ │ └── Api │ │ │ ├── Api.csproj │ │ │ ├── Application │ │ │ ├── Common │ │ │ │ ├── AssemblyScanner.cs │ │ │ │ ├── Behaviours │ │ │ │ │ ├── DbUtil.cs │ │ │ │ │ ├── RequestPerformanceBehaviour.cs │ │ │ │ │ ├── RequestValidationBehavior.cs │ │ │ │ │ └── SqlDatabaseBehaviour.cs │ │ │ │ ├── Extensions │ │ │ │ │ ├── ObjectExtensions.cs │ │ │ │ │ └── QueryableExtensions.cs │ │ │ │ ├── Interfaces │ │ │ │ │ ├── ICurrentUserService.cs │ │ │ │ │ ├── IQueryDb.cs │ │ │ │ │ └── IUnitOfWork.cs │ │ │ │ ├── Mappings │ │ │ │ │ ├── IMapFrom.cs │ │ │ │ │ ├── IMapToAndFrom.cs │ │ │ │ │ └── MappingProfile.cs │ │ │ │ ├── Paging │ │ │ │ │ ├── PagedList.cs │ │ │ │ │ └── PagedQuery.cs │ │ │ │ └── Validation │ │ │ │ │ ├── CommandBase.cs │ │ │ │ │ ├── IRequireValidation.cs │ │ │ │ │ ├── PrimaryKeyValidator.cs │ │ │ │ │ ├── ValidationExtensions.cs │ │ │ │ │ └── ValidationService.cs │ │ │ ├── DependencyInjection.cs │ │ │ └── Features │ │ │ │ ├── MasterFiles │ │ │ │ ├── DisposalReasonDto.cs │ │ │ │ ├── MasterFileDto.cs │ │ │ │ ├── WeatherConditionDto.cs │ │ │ │ └── WeatherTypeDto.cs │ │ │ │ └── ToDoItems │ │ │ │ ├── CreateTodoItemCommand.cs │ │ │ │ ├── DeleteTodoItemCommand.cs │ │ │ │ ├── EmailValidator.cs │ │ │ │ ├── GetAllToDosQuery.cs │ │ │ │ ├── GetToDoItemQuery.cs │ │ │ │ ├── ToDoItemDetailDto.cs │ │ │ │ ├── ToDoItemDto.cs │ │ │ │ └── UpdateTodoItemCommand.cs │ │ │ ├── Common │ │ │ ├── ActionResults.cs │ │ │ ├── AspNetResultExtensions.cs │ │ │ ├── CustomExceptionHandlerMiddleware.cs │ │ │ └── HostingEnvironmentExtensions.cs │ │ │ ├── Configuration │ │ │ ├── AppServiceProviderFactory.cs │ │ │ └── Options │ │ │ │ └── IdentityServerOptions.cs │ │ │ ├── Contracts │ │ │ ├── ApiRoutes.cs │ │ │ └── Responses │ │ │ │ ├── ErrorResponse.cs │ │ │ │ ├── RecordsCreatedResponse.cs │ │ │ │ └── SuccessResponse.cs │ │ │ ├── Controllers │ │ │ ├── ApiController.cs │ │ │ ├── InfoController.cs │ │ │ ├── MasterFiles │ │ │ │ ├── MasterFileControllerFor.cs │ │ │ │ ├── MasterFileControllers.cs │ │ │ │ ├── ReadDeleteControllerFor.cs │ │ │ │ └── WeatherConditionController.cs │ │ │ └── ToDoController.cs │ │ │ ├── DependencyInjection.cs │ │ │ ├── Domain │ │ │ ├── Common │ │ │ │ ├── Entity.cs │ │ │ │ ├── FluentResult │ │ │ │ │ ├── AppError.cs │ │ │ │ │ ├── AppWarning.cs │ │ │ │ │ ├── IFailure.cs │ │ │ │ │ ├── RecordsCreatedSuccess.cs │ │ │ │ │ ├── RecordsNotFoundAppError.cs │ │ │ │ │ ├── ResultExtensions.cs │ │ │ │ │ ├── Resultz.cs │ │ │ │ │ ├── TreatWarningsAsErrors.cs │ │ │ │ │ ├── ValidationMessages.cs │ │ │ │ │ ├── ValidationSeverity.cs │ │ │ │ │ └── Warning.cs │ │ │ │ ├── Guards │ │ │ │ │ └── Ensure.cs │ │ │ │ ├── MasterFile.cs │ │ │ │ ├── SystemTime.cs │ │ │ │ └── ValueObject.cs │ │ │ ├── Model │ │ │ │ ├── MasterFiles │ │ │ │ │ ├── DisposalReason.cs │ │ │ │ │ ├── WeatherCondition.cs │ │ │ │ │ └── WeatherType.cs │ │ │ │ ├── SharedValueObjects │ │ │ │ │ ├── ChemicalRateQuantity.cs │ │ │ │ │ ├── PositiveDecimal.cs │ │ │ │ │ └── PositiveInteger.cs │ │ │ │ └── ToDos │ │ │ │ │ ├── Email.cs │ │ │ │ │ └── ToDoItem.cs │ │ │ └── Utils │ │ │ │ ├── EnumerableExtensions.cs │ │ │ │ └── ExceptionExtensions.cs │ │ │ ├── Infrastructure │ │ │ ├── DependencyInjection.cs │ │ │ ├── HealthChecks │ │ │ │ ├── DependencyHealthCheck.cs │ │ │ │ ├── EndpointRouteBuilderExtensions.cs │ │ │ │ ├── LiveHealthCheck.cs │ │ │ │ └── ReadyHealthCheck.cs │ │ │ ├── Identity │ │ │ │ └── CurrentUserService.cs │ │ │ ├── Logging │ │ │ │ ├── LoggingHelper.cs │ │ │ │ ├── SerilogApplicationBuilderExtensions.cs │ │ │ │ └── SerilogLoggingActionFilter.cs │ │ │ └── Persistence │ │ │ │ ├── AppDbContext.cs │ │ │ │ ├── Configurations │ │ │ │ ├── DisposalReasonConfiguration.cs │ │ │ │ ├── TodoItemConfiguration.cs │ │ │ │ ├── WeatherConditionConfiguration.cs │ │ │ │ └── WeatherTypeConfiguration.cs │ │ │ │ ├── QueryDbContext.cs │ │ │ │ └── ShadowProperties.cs │ │ │ ├── Program.cs │ │ │ ├── Properties │ │ │ └── launchSettings.json │ │ │ ├── README.md │ │ │ ├── Startup.cs │ │ │ ├── SwaggerExamples │ │ │ ├── Requests │ │ │ │ └── CreateTodoItemCommandExample.cs │ │ │ └── Responses │ │ │ │ ├── RecordCreatedResponseExample.cs │ │ │ │ └── ToDoItemDtoExample.cs │ │ │ ├── appsettings.Test.json │ │ │ ├── appsettings.json │ │ │ └── appsettings.ncrunch.json │ └── specs │ │ ├── Specs.Acceptance │ │ ├── AcceptanceBootstrapper.cs │ │ ├── Application │ │ │ ├── Aspects │ │ │ │ ├── DevOpsStory.cs │ │ │ │ ├── HealthyLivenessCheck.cs │ │ │ │ └── HealthyReadinessCheck.cs │ │ │ ├── Features │ │ │ │ └── MasterFiles │ │ │ │ │ └── ReadMe.md │ │ │ └── ReadMe.md │ │ ├── README.md │ │ ├── ScenarioFor.cs │ │ ├── Specs.Acceptance.csproj │ │ ├── _CurrentSprint │ │ │ ├── MasterFiles │ │ │ │ ├── DisposalReasons │ │ │ │ │ ├── CreateMany │ │ │ │ │ │ ├── CannotUpdateDisposalReasonWhenCreating.cs │ │ │ │ │ │ ├── CreateMultipleDisposalReasonsWithInvalidData.cs │ │ │ │ │ │ ├── CreateMultipleDisposalReasonsWithValidData.cs │ │ │ │ │ │ ├── CreateSingleDisposalReasonWithInvalidData.cs │ │ │ │ │ │ ├── CreateSingleDisposalReasonWithValidData.cs │ │ │ │ │ │ └── EachDisposalReasonHasUniqueReason.cs │ │ │ │ │ ├── DeleteOne │ │ │ │ │ │ ├── DeleteExistingDisposalReason.cs │ │ │ │ │ │ └── DeleteNonExistentDisposalReason.cs │ │ │ │ │ ├── DisposalReasonStory.cs │ │ │ │ │ ├── GetAll │ │ │ │ │ │ ├── GetAllDisposalReasonsOrdering.cs │ │ │ │ │ │ ├── GetAllDisposalReasonsPaging.cs │ │ │ │ │ │ ├── GetAllDisposalReasonsWhenNone.cs │ │ │ │ │ │ ├── GetAllDisposalReasonsWhenSome.cs │ │ │ │ │ │ └── GetAllDisposalReasonsWithInvalidQuery.cs │ │ │ │ │ ├── GetOne │ │ │ │ │ │ ├── GetOneExistingDisposalReason.cs │ │ │ │ │ │ └── GetOneNonExistingDisposalReason.cs │ │ │ │ │ └── UpdateMany │ │ │ │ │ │ ├── UpdateCannotCreateDisposalReason.cs │ │ │ │ │ │ ├── UpdateMultipleDisposalReasonsWithInvalidData.cs │ │ │ │ │ │ ├── UpdateMultipleDisposalReasonsWithValidData.cs │ │ │ │ │ │ ├── UpdateNonExistentDisposalReason.cs │ │ │ │ │ │ ├── UpdateSingleDisposalReasonWithInvalidData.cs │ │ │ │ │ │ └── UpdateSingleDisposalReasonWithValidData.cs │ │ │ │ ├── WeatherConditions │ │ │ │ │ └── WeatherConditionSpecs.cs │ │ │ │ ├── WeatherTypes │ │ │ │ │ └── WeatherTypeSpecs.cs │ │ │ │ └── _BaseSpecifications │ │ │ │ │ ├── CreateManySpecs.cs │ │ │ │ │ ├── DeleteOneSpecs.cs │ │ │ │ │ ├── GetAllSpecs.cs │ │ │ │ │ ├── GetOneSpecs.cs │ │ │ │ │ ├── MasterFilesStory.cs │ │ │ │ │ └── UpdateManySpecs.cs │ │ │ └── ReadMe.md │ │ └── _TemporarySampleSpecs │ │ │ ├── ReadMe.md │ │ │ └── ToDoItems │ │ │ ├── Create │ │ │ ├── CreateInvalidToDo.cs │ │ │ └── CreateValidToDo.cs │ │ │ ├── Delete │ │ │ ├── DeleteNonExistingToDo.cs │ │ │ └── ValidDelete.cs │ │ │ ├── GetAll │ │ │ ├── GetAllToDos.cs │ │ │ └── GetAllToDosWhenNone.cs │ │ │ ├── GetOne │ │ │ ├── GetExistingToDo.cs │ │ │ └── GetNonExistingToDo.cs │ │ │ ├── ToDoStory.cs │ │ │ └── Update │ │ │ ├── InvalidUpdate.cs │ │ │ ├── UpdateNonExistentToDo.cs │ │ │ └── ValidUpdate.cs │ │ ├── Specs.Integration │ │ ├── Api │ │ │ └── Configuration │ │ │ │ └── AutofacConventions.cs │ │ ├── Application │ │ │ └── Features │ │ │ │ └── MasterFiles │ │ │ │ └── Validators │ │ │ │ └── DisplayReasonValidatorSpecs.cs │ │ ├── Infrastructure │ │ │ └── Persistence │ │ │ │ ├── DbResilienceSpecs.cs │ │ │ │ ├── SqlExceptionFactory.cs │ │ │ │ └── TransientFailureCausingCommandInterceptor.cs │ │ ├── IntegrationBootstrapper.cs │ │ ├── ScenarioFor.cs │ │ ├── Specs.Integration.csproj │ │ └── TestsFor.cs │ │ ├── Specs.Library │ │ ├── Builders │ │ │ ├── Dtos │ │ │ │ └── WeatherConditionDtoBuilder.cs │ │ │ ├── Entities │ │ │ │ ├── MasterFiles │ │ │ │ │ ├── DisposalReasonBuilder.cs │ │ │ │ │ ├── MasterFileBuilder.cs │ │ │ │ │ └── WeatherConditionBuilder.cs │ │ │ │ └── ToDoItemBuilder.cs │ │ │ ├── Get.cs │ │ │ ├── ObjectMothers │ │ │ │ ├── SequentialMother.cs │ │ │ │ └── Stubs.cs │ │ │ └── ValueSuppliers │ │ │ │ └── CodeValueSupplier.cs │ │ ├── Data │ │ │ ├── DatabaseApplicationAction.cs │ │ │ ├── EntityExtensions.cs │ │ │ ├── IDb.cs │ │ │ ├── IDbFactory.cs │ │ │ ├── ResetDatabaseAction.cs │ │ │ ├── ResetSystemTimeAction.cs │ │ │ └── SqlServer │ │ │ │ ├── SqlDb.cs │ │ │ │ └── SqlDbFactory.cs │ │ ├── Drivers │ │ │ └── Api │ │ │ │ ├── ApiDriver.cs │ │ │ │ ├── ApiDriverException.cs │ │ │ │ ├── ApiResponse.cs │ │ │ │ ├── AsyncApiDriver.cs │ │ │ │ ├── ITestHost.cs │ │ │ │ ├── JsonExtensions.cs │ │ │ │ ├── MemoryHost.cs │ │ │ │ └── WebHost.cs │ │ ├── Extensions │ │ │ ├── ApiResponseAssertions.cs │ │ │ ├── ObjectExtensions.cs │ │ │ ├── ResultSpecExtensions.cs │ │ │ ├── ServiceCollectionExtensions.cs │ │ │ ├── StringExtensions.cs │ │ │ └── ValidationTestUtil.cs │ │ ├── Helpers │ │ │ ├── AsyncHelper.cs │ │ │ ├── ExecuteScenarioAttribute.cs │ │ │ ├── FluentValidationHelpers.cs │ │ │ ├── ProjectLocation.cs │ │ │ └── TestableSystemTime.cs │ │ ├── Identity │ │ │ └── TestCurrentUserService.cs │ │ ├── README.md │ │ ├── Specs.Library.csproj │ │ └── TestSettings.cs │ │ └── Specs.Unit │ │ ├── Api │ │ └── Common │ │ │ └── AspNetResultExtensions │ │ │ └── ToCreatedResultSpecs.cs │ │ ├── Application │ │ ├── Common │ │ │ ├── Mappings │ │ │ │ └── AutoMapperSpecs.cs │ │ │ └── Validation │ │ │ │ ├── ResultSpecs.cs │ │ │ │ └── ValidationServiceSpecs.cs │ │ └── Features │ │ │ └── ToDoItems │ │ │ └── Create │ │ │ └── CreateToDoItemCommandValidatorSpecs.cs │ │ ├── Domain │ │ ├── Common │ │ │ ├── EnsureSpecs.cs │ │ │ └── FluentResult │ │ │ │ ├── GetReasonOrDefaultSpecs.cs │ │ │ │ ├── GetReasonSpecs.cs │ │ │ │ └── ResultExtensionsSpecs.cs │ │ └── Model │ │ │ └── SharedValueObjects │ │ │ └── ChemicalRateQuantitySpecs.cs │ │ ├── Infrastructure │ │ └── Persistence │ │ │ └── Paging │ │ │ └── PagedQuerySpecs.cs │ │ ├── Samples │ │ ├── OrderProcessing.cs │ │ ├── OrderProcessorSpecs.cs │ │ └── ProcessingAnOrderWithoutAutoMocking.cs │ │ ├── ScenarioFor.cs │ │ ├── Specs.Unit.csproj │ │ ├── TestsFor.cs │ │ └── UnitBootstrapper.cs └── Specify.Samples │ ├── Domain │ ├── Atm │ │ ├── Atm.cs │ │ ├── Card.cs │ │ └── DisplayMessage.cs │ ├── Katas │ │ └── PrimeNumbers │ │ │ └── PrimeCalculator.cs │ ├── OrderProcessing │ │ ├── Auditer.cs │ │ ├── IInventory.cs │ │ ├── Order.cs │ │ ├── OrderProcessor.cs │ │ ├── OrderResult.cs │ │ ├── OrderSubmitted.cs │ │ └── Publisher.cs │ ├── TicTacToe │ │ └── Game.cs │ └── TrainFares │ │ ├── BuyerCategory.cs │ │ ├── DayPass.cs │ │ ├── Fare.cs │ │ ├── Money.cs │ │ ├── MonthlyPass.cs │ │ ├── SingleTicket.cs │ │ └── WeeklyPass.cs │ ├── ScenarioFor.cs │ ├── Specify.Samples.csproj │ ├── SpecifyBootstrapper.cs │ └── Specs │ ├── ATM │ ├── AccountHasInsufficientFunds.cs │ ├── AccountHolderWithdrawsCash.cs │ ├── AccountHolderWithdrawsCashStory.cs │ ├── AtmHtmlReportConfig.cs │ └── CardHasBeenDisabled.cs │ ├── OrderProcessing │ └── OrderProcessorSpecs.cs │ ├── TrainFares │ └── BuyingTrainFareWithExamples.cs │ └── UseExamplesWithReflectiveApi.cs ├── Settings.StyleCop ├── Specify.sln ├── Specify.sln.GhostDoc.xml ├── _NCrunch_Specify └── StoredText │ └── 2acd2f59abe542e5a0767534a8ca874f ├── app ├── Specify.Autofac │ ├── AutofacContainer.cs │ ├── AutofacImmutableContainerException.cs │ ├── AutofacMockRegistrationHandler.cs │ ├── ContainerBuilderExtensions.cs │ ├── DefaultAutofacBootstrapper.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Specify.Autofac.csproj │ └── SpecifyAutofacConfigScanner.cs └── Specify │ ├── Catch.cs │ ├── Config.cs │ ├── Configuration │ ├── BddfyTestEngine.cs │ ├── BootstrapperBase.cs │ ├── DefaultBootstrapper.cs │ ├── Examples │ │ ├── ExampleScope.cs │ │ ├── IExampleScope.cs │ │ └── NullExampleScope.cs │ ├── ExecutableAttributes │ │ ├── BeginTestCaseAttribute.cs │ │ └── EndTestCaseAttribute.cs │ ├── IBootstrapSpecify.cs │ ├── IPerApplicationAction.cs │ ├── IPerScenarioAction.cs │ ├── ITestEngine.cs │ ├── ReportConfiguration.cs │ ├── Scanners │ │ ├── ConfigScanner.cs │ │ ├── ConfigScannerFactory.cs │ │ ├── IConfigScanner.cs │ │ └── SpecifyConfigScanner.cs │ ├── ScenarioLoggingProcessor.cs │ ├── ScenarioRunner.cs │ ├── SpecifyStoryMetadataScanner.cs │ └── StepScanners │ │ ├── ArgumentCleaningExtensions.cs │ │ └── SpecifyExecutableAttributeStepScanner.cs │ ├── ContainerFor.cs │ ├── Containers │ ├── DryContainer.cs │ ├── DryContainerFactory.cs │ └── DryMockingContainer.cs │ ├── Exceptions │ ├── InterfaceRegistrationException.cs │ ├── InterfaceResolutionException.cs │ └── LoggingException.cs │ ├── Extensions │ ├── EnumerableExtensions.cs │ ├── ExceptionExtensions.cs │ ├── ObjectExtensions.cs │ ├── ScenarioExtensions.cs │ ├── StringExtensions.cs │ └── TypeExtensions.cs │ ├── Host.cs │ ├── IContainer.cs │ ├── IScenario.cs │ ├── LoggingGateway.cs │ ├── Mocks │ ├── AutoMockerFor.cs │ ├── FakeItEasyMockFactory.cs │ ├── FileSystem.cs │ ├── IFileSystem.cs │ ├── IMockFactory.cs │ ├── MockDetector.cs │ ├── MockFactoryBase.cs │ ├── MoqMockFactory.cs │ ├── NSubstituteMockFactory.cs │ └── NullMockFactory.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── ScenarioFor.cs │ ├── Specify.csproj │ ├── Stories │ ├── SpecificationStory.cs │ ├── Story.cs │ ├── UserStory.cs │ └── ValueStory.cs │ ├── TestsFor.cs │ └── lib │ └── AssemblyTypeResolver.cs └── tests ├── Specify.IntegrationTests ├── ConfigScanners │ └── SpecifyAutofacConfigScannerTests.cs ├── ContainerFors │ ├── AutoMocking │ │ ├── AutoMockerContainerForTests.cs │ │ ├── AutofacMockingContainerForTests.cs │ │ └── DryMockingContainerForTests.cs │ ├── ContainerForIntegrationTestsBase.cs │ └── Ioc │ │ ├── AutofacContainerForGetTests.cs │ │ └── DryContainerForIntegrationTests.cs ├── Containers │ ├── AutoMocking │ │ ├── AutofacMockingContainerTests.cs │ │ ├── DryMockingContainerTests.cs │ │ └── MockingContainerTestsFor.cs │ ├── ContainerFactory.cs │ ├── ContainerSpecsFor.cs │ └── Ioc │ │ ├── Application │ │ ├── ApplicationContainerTestsFor.cs │ │ ├── AutofacApplicationContainerTests.cs │ │ └── DryApplicationContainerTests.cs │ │ ├── AutofacContainerTests.cs │ │ ├── DryContainerTests.cs │ │ ├── ExamplesLifecycle.cs │ │ ├── IocContainerTestsFor.cs │ │ └── ScenarioConstructorInjectionTests.cs ├── IntegrationBootstrapper.cs ├── Mocks │ ├── FakeItEasyMockFactoryTests.cs │ ├── FileSystemTests.cs │ ├── MockFactoryTestsFor.cs │ ├── MoqMockFactoryTests.cs │ └── NSubstituteMockFactoryTests.cs └── Specify.IntegrationTests.csproj └── Specify.Tests ├── Apis ├── SemanticVersioningTests.cs ├── SemanticVersioningTests.specify_autofac_has_no_public_api_changes.approved.txt └── SemanticVersioningTests.specify_has_no_public_api_changes.approved.txt ├── Configuration ├── BddfyTestEngineTests.cs ├── BddfyTestEngineTests.should_display_examples_on_reports.approved.txt ├── LoggingProcessorTests.LoggingConfigurationTest.approved.txt ├── LoggingProcessorTests.LoggingOutputTest.approved.txt ├── LoggingProcessorTests.cs ├── ReportTestData.cs ├── Scanners │ ├── ConfigScannerFactoryTests.cs │ └── SpecifyConfigScannerTests.cs ├── ScenarioRunnerTests.cs ├── SpecifyStoryMetadataScannerTests.cs ├── StubPerScenarioAction.cs └── TemporaryNLogLogger.cs ├── Mocks ├── FakeItEasyMockFactoryTests.cs ├── MockDetectorTests.cs ├── MockFactoryTestsFor.cs ├── MoqMockFactoryTests.cs └── NSubstituteMockFactoryTests.cs ├── Properties └── InternalsVisibleTo.cs ├── Specify.Tests.csproj ├── SpecifyRootFolder ├── CatchSpecs.cs ├── ContainerForTests.cs ├── LoggingGatewayTests.cs ├── ScenarioExtensionsTests.cs ├── ScenarioForTests.cs └── TypeExtensionTests.cs ├── Stories ├── UserStoryTests.cs └── ValueStoryTests.cs ├── Stubs ├── AutoMockingStubs.cs ├── Scenarios.cs ├── TestableUnitScenario.cs ├── TestableUserStoryScenario.cs ├── UnitScenarioWithAllSupportedStepsInRandomOrder.cs ├── UnitScenarioWithAllSupportedStepsInRandomOrderWithExamples.cs ├── UserStories.cs └── UserStoryScenarioWithAllSupportedStepsInRandomOrder.cs ├── TestsFor.cs └── nlog-sample.txt /.gitignore: -------------------------------------------------------------------------------- 1 | _ReSharper.* 2 | bin/ 3 | obj/ 4 | *.user 5 | *.suo 6 | *~ 7 | *.swp 8 | *.orig 9 | *.nupkg 10 | *.crunchproject.local.xml 11 | *.crunchsolution.local.xml 12 | *.ncrunchsolution 13 | *.ncrunchproject 14 | *.cache 15 | tools/* 16 | artifacts/* 17 | PackageBuild/* 18 | Build/* 19 | *.mdf 20 | *.ldf 21 | *.sdf 22 | *.vsdoc 23 | TestResult.xml 24 | *.DotSettings 25 | 26 | # Visual Studio 2015 cache/options directory 27 | .vs/ -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: ContinuousDelivery 2 | next-version: 2.4.0 3 | branches: {} 4 | ignore: 5 | sha: [] 6 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | assembly_info: 2 | patch: false 3 | 4 | configuration: 5 | - Release 6 | 7 | build_script: 8 | - ps: .\build.ps1 9 | 10 | test: off 11 | skip_tags: true 12 | 13 | cache: 14 | - src\packages -> **\packages.config # preserve "packages" directory in the root of build folder but will reset it if packages.config is modified 15 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # .NET Desktop 2 | # Build and run tests for .NET Desktop or Windows classic desktop solutions. 3 | # Add steps that publish symbols, save build artifacts, and more: 4 | # https://docs.microsoft.com/vsts/pipelines/apps/windows/dot-net 5 | 6 | pool: 7 | vmImage: 'VS2017-Win2016' 8 | 9 | variables: 10 | solution: '**/*.sln' 11 | buildPlatform: 'Any CPU' 12 | buildConfiguration: 'Release' 13 | 14 | steps: 15 | - task: NuGetToolInstaller@0 16 | 17 | - task: NuGetCommand@2 18 | inputs: 19 | restoreSolution: '$(solution)' 20 | 21 | - task: VSBuild@1 22 | inputs: 23 | solution: '$(solution)' 24 | platform: '$(buildPlatform)' 25 | configuration: '$(buildConfiguration)' 26 | 27 | - task: VSTest@2 28 | inputs: 29 | platform: '$(buildPlatform)' 30 | configuration: '$(buildConfiguration)' 31 | -------------------------------------------------------------------------------- /bddfy-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhelan/Specify/7a6f02a3d497ef5567976e78caa151ab334433d8/bddfy-report.png -------------------------------------------------------------------------------- /docs/concepts-context-specification.md: -------------------------------------------------------------------------------- 1 | # Context-Specification 2 | With context-specification you have a class per scenario, with each step having its own method and state being shared between methods in fields. This means that the setup and execution only happen once (the context), and then each `Then` method is a specification that asserts against the result of the execution. 3 | 4 | ## Scenarios 5 | With functional tests, user stories can be seen as placeholders for conversations that are purposely kept terse while in the backlog (Desirements). 6 | 7 | Scenarios document the conversation that took place with the customer (Requirements). They are the product owner’s acceptance criteria for the user story. 8 | 9 | ## Scenarios are Specifications 10 | Functional test scenarios are high-level specifications of what the system should do. They document the conversation with the business. 11 | 12 | Unit test scenarios are low-level specifications of how the system works. They are a conversation amongst the developers. 13 | 14 | ## Scenarios should focus on business rules 15 | All the steps should reflect the business rules (the what), not the technical implementation (the how). 16 | 17 | One suggestion is to [pretend there is no UI](http://itsadeliverything.com/declarative-vs-imperative-gherkin-scenarios-for-cucumber). -------------------------------------------------------------------------------- /docs/concepts-system-under-test.md: -------------------------------------------------------------------------------- 1 | This is a term from the book [xUnit Patterns](http://xunitpatterns.com/), by Gerard Meszaros. 2 | 3 | > The "system under test". It is short for "whatever thing we are testing" and is always defined from the perspective of the test. When we are writing unit tests the system under test (SUT) is whatever class (a.k.a. CUT), object (a.k.a. OUT) or method(s) (a.k.a. MUT) we are testing; when we are writing customer tests, the SUT is probably the entire application (a.k.a. AUT) or at least a major subsystem of it. The parts of the application that we are not verifying in this particular test may still be involved as a depended-on component (DOC). 4 | 5 | Also known as 6 | 7 | - Application Under Test (AUT) 8 | - Method Under Test (MUT) 9 | - Class Under Test (CUT) 10 | 11 | Erik Dietrich describes the SUT as [Target](http://www.daedtech.com/test-readability-best-of-all-worlds/). -------------------------------------------------------------------------------- /docs/consistent-reporting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhelan/Specify/7a6f02a3d497ef5567976e78caa151ab334433d8/docs/consistent-reporting.png -------------------------------------------------------------------------------- /docs/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhelan/Specify/7a6f02a3d497ef5567976e78caa151ab334433d8/docs/github.png -------------------------------------------------------------------------------- /docs/nuget-install-package-specify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhelan/Specify/7a6f02a3d497ef5567976e78caa151ab334433d8/docs/nuget-install-package-specify.png -------------------------------------------------------------------------------- /docs/nuget-manage-nuget-packages-specify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhelan/Specify/7a6f02a3d497ef5567976e78caa151ab334433d8/docs/nuget-manage-nuget-packages-specify.png -------------------------------------------------------------------------------- /docs/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhelan/Specify/7a6f02a3d497ef5567976e78caa151ab334433d8/docs/twitter.png -------------------------------------------------------------------------------- /docs/visualstudio_enablesourceserversupport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhelan/Specify/7a6f02a3d497ef5567976e78caa151ab334433d8/docs/visualstudio_enablesourceserversupport.png -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2014 Michael Whelan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Specify-DotNet 2 | theme: readthedocs 3 | markdown_extensions: [fenced_code] 4 | pages: 5 | - Home: index.md 6 | - Features: 7 | - Overview: features-overview.md 8 | - Using Specify: 9 | - Getting Started: using-specify-getting-started.md 10 | - Configuration: using-specify-configuration.md 11 | - Scenario Lifecycle: using-specify-scenario-lifecycle.md 12 | - Supported Test Frameworks: using-specify-supported-test-frameworks.md 13 | - Concepts: 14 | - SUT (System Under Test): concepts-system-under-test.md 15 | - Context Specification: concepts-context-specification.md -------------------------------------------------------------------------------- /nuget/Specify.Autofac.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Specify.Autofac 5 | $version$ 6 | Specify.Autofac 7 | Michael Whelan 8 | Michael Whelan 9 | http://specify-dotnet.readthedocs.org/ 10 | false 11 | Specify is a .Net testing library that builds on top of BDDfy from TestStack. Specify.Autofac provides Autofac adapters for IoC and automocking. 12 | The baseline for Specify 13 | Copyright 2015 Michael Whelan 14 | 15 | 16 | 17 | 18 | https://github.com/mwhelan/Specify/blob/master/license.txt 19 | TDD BDD Testing BDDfy 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /nuget/Specify.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Specify 5 | $version$ 6 | Specify 7 | Michael Whelan 8 | Michael Whelan 9 | http://specify-dotnet.readthedocs.org/ 10 | false 11 | Specify is a .Net testing library that builds on top of BDDfy from TestStack 12 | The baseline for Specify 13 | Copyright 2017 Michael Whelan 14 | 15 | 16 | 17 | https://github.com/mwhelan/Specify/blob/master/license.txt 18 | TDD BDD Testing BDDfy 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /nuget/SpecifyAutofacBootstrapper.cs.pp: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Specify.Autofac; 3 | 4 | namespace $rootnamespace$ 5 | { 6 | /// 7 | /// The startup class to configure Specify with the Autofac container. 8 | /// Make any changes to the default configuration settings in this file. 9 | /// 10 | public class SpecifyAutofacBootstrapper : DefaultAutofacBootstrapper 11 | { 12 | /// 13 | /// Register any additional items into the Autofac container using the ContainerBuilder or leave it as it is. 14 | /// 15 | /// The Autofac . 16 | public override void ConfigureContainer(ContainerBuilder builder) 17 | { 18 | 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /nuget/SpecifyBootstrapper.cs.pp: -------------------------------------------------------------------------------- 1 | using Specify.Configuration; 2 | using TinyIoC; 3 | 4 | namespace $rootnamespace$ 5 | { 6 | /// 7 | /// The startup class to configure Specify with the default TinyIoc container. 8 | /// Make any changes to the default configuration settings in this file. 9 | /// 10 | public class SpecifyBootstrapper : DefaultBootstrapper 11 | { 12 | /// 13 | /// Register any additional items into the TinyIoc container or leave it as it is. 14 | /// 15 | /// The container. 16 | public override void ConfigureContainer(TinyIoCContainer container) 17 | { 18 | 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/NuGet.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Common/Extensions/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | 4 | namespace ApiTemplate.Api.Application.Common.Extensions 5 | { 6 | public static class ObjectExtensions 7 | { 8 | public static string ToJson(this object entity) 9 | { 10 | return JsonConvert.SerializeObject(entity); 11 | } 12 | 13 | public static List ToCollection(this T item) where T : new() 14 | { 15 | return new List{item}; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Common/Interfaces/ICurrentUserService.cs: -------------------------------------------------------------------------------- 1 | namespace ApiTemplate.Api.Application.Common.Interfaces 2 | { 3 | public interface ICurrentUserService 4 | { 5 | int UserId { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Common/Interfaces/IQueryDb.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using ApiTemplate.Api.Domain.Common; 3 | using ApiTemplate.Api.Domain.Model.ToDos; 4 | 5 | namespace ApiTemplate.Api.Application.Common.Interfaces 6 | { 7 | // This is used by Query Handlers in CQRS 8 | public interface IQueryDb 9 | { 10 | IQueryable ToDoItems { get; } 11 | 12 | // This is a generic alternative to above. Team should discuss preference. 13 | IQueryable QueryFor() where T : Entity; 14 | } 15 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Common/Interfaces/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using ApiTemplate.Api.Domain.Model.MasterFiles; 5 | using ApiTemplate.Api.Domain.Model.ToDos; 6 | using Microsoft.EntityFrameworkCore; 7 | using Microsoft.EntityFrameworkCore.Storage; 8 | 9 | namespace ApiTemplate.Api.Application.Common.Interfaces 10 | { 11 | // This is used by Command Handlers in CQRS 12 | public interface IUnitOfWork 13 | { 14 | DbSet ToDoItems { get; set; } 15 | DbSet WeatherConditions { get; set; } 16 | 17 | Task SaveChangesAsync(CancellationToken cancellationToken); 18 | IExecutionStrategy CreateExecutionStrategy(); 19 | Task BeginTransactionAsync(IsolationLevel level = IsolationLevel.ReadCommitted); 20 | Task CommitTransactionAsync(); 21 | void RollbackTransaction(); 22 | } 23 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Common/Mappings/IMapFrom.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | 3 | namespace ApiTemplate.Api.Application.Common.Mappings 4 | { 5 | public interface IMapFrom 6 | { 7 | void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Common/Mappings/IMapToAndFrom.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | 3 | namespace ApiTemplate.Api.Application.Common.Mappings 4 | { 5 | public interface IMapToAndFrom 6 | { 7 | void Mapping(Profile profile) => profile 8 | .CreateMap( GetType(), typeof(T)) 9 | .ReverseMap(); 10 | } 11 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Common/Mappings/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using AutoMapper; 5 | 6 | namespace ApiTemplate.Api.Application.Common.Mappings 7 | { 8 | public class MappingProfile : Profile 9 | { 10 | private Assembly _assembly; 11 | 12 | public MappingProfile() 13 | { 14 | _assembly = Assembly.GetExecutingAssembly(); 15 | 16 | ApplyMapFromMappings(typeof(IMapFrom<>), "IMapFrom`1"); 17 | 18 | var type = typeof(IMapToAndFrom<>); 19 | 20 | ApplyMapFromMappings(typeof(IMapToAndFrom<>), "IMapToAndFrom`1"); 21 | } 22 | 23 | private void ApplyMapFromMappings(Type interfaceType, string interfaceName) 24 | { 25 | var types = _assembly.GetExportedTypes() 26 | .Where(t => t.GetInterfaces().Any(i => 27 | i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType)) 28 | .ToList(); 29 | 30 | foreach (var type in types) 31 | { 32 | var instance = Activator.CreateInstance(type); 33 | 34 | var methodInfo = type.GetMethod("Mapping") 35 | ?? type.GetInterface(interfaceName).GetMethod("Mapping"); 36 | 37 | methodInfo?.Invoke(instance, new object[] { this }); 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Common/Paging/PagedList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace ApiTemplate.Api.Application.Common.Paging 6 | { 7 | public class PagedList 8 | { 9 | public int CurrentPage { get; set; } 10 | public int PageCount { get; set; } 11 | public int PageSize { get; set; } 12 | public int RowCount { get; set; } 13 | public List Results { get; set; } 14 | 15 | public PagedList() { } 16 | 17 | public PagedList(int page, int? pageSize, List data) 18 | { 19 | CurrentPage = page; 20 | RowCount = data.Count(); 21 | PageSize = pageSize == null ? RowCount : (int)pageSize; // If we don't pass in a page size then we want it all 22 | 23 | var pageCount = (double)RowCount / (PageSize == 0 ? 25 : PageSize); // Default our page size to 25 if we end up with zero 24 | PageCount = (int)Math.Ceiling(pageCount); 25 | if (PageCount == 0) 26 | PageCount = 1; 27 | 28 | if (PageCount < CurrentPage) 29 | CurrentPage = PageCount; 30 | 31 | var skip = (CurrentPage - 1) * PageSize; 32 | 33 | Results = data.Skip(skip).Take(PageSize).ToList(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Common/Paging/PagedQuery.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Domain.Common.FluentResult; 2 | using FluentResults; 3 | 4 | namespace ApiTemplate.Api.Application.Common.Paging 5 | { 6 | public class PagedQuery 7 | { 8 | public int Page { get; set; } = 1; 9 | public int? PageSize { get; set; } = null; 10 | public string OrderBy { get; set; } = null; 11 | public bool OrderByDesc { get; set; } = false; 12 | public string FilterField { get; set; } = null; 13 | public string FilterText { get; set; } = null; 14 | 15 | public bool HasSearch => FilterField != null && FilterText != null; 16 | 17 | public Result IsValid() 18 | { 19 | if (FilterField != null && FilterText is null) 20 | { 21 | return Resultz.Error("FilterText", ValidationMessages.IsRequiredFor(nameof(FilterText))); 22 | } 23 | 24 | return Result.Ok(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Common/Validation/CommandBase.cs: -------------------------------------------------------------------------------- 1 | using FluentResults; 2 | using MediatR; 3 | 4 | namespace ApiTemplate.Api.Application.Common.Validation 5 | { 6 | public class CommandBase : IRequest 7 | { 8 | public bool IgnoreWarnings { get; set; } = false; 9 | } 10 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Common/Validation/IRequireValidation.cs: -------------------------------------------------------------------------------- 1 | namespace ApiTemplate.Api.Application.Common.Validation 2 | { 3 | public interface IRequireValidation 4 | { 5 | public int Id { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Common/Validation/PrimaryKeyValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using FluentValidation.Results; 3 | 4 | namespace ApiTemplate.Api.Application.Common.Validation 5 | { 6 | public abstract class PrimaryKeyValidator : AbstractValidator 7 | where T : IRequireValidation 8 | { 9 | public override ValidationResult Validate(ValidationContext context) 10 | { 11 | var result = base.Validate(context); 12 | InjectPrimaryKeyToValidationFailure(result, context); 13 | return result; 14 | } 15 | 16 | //public override async Task ValidateAsync(ValidationContext context, CancellationToken cancellation = new CancellationToken()) 17 | //{ 18 | // var result = await base.ValidateAsync(context, cancellation); 19 | // InjectPrimaryKeyToValidationFailure(result, context); 20 | // return result; 21 | //} 22 | 23 | private void InjectPrimaryKeyToValidationFailure(ValidationResult result, ValidationContext context) 24 | { 25 | foreach (var failure in result.Errors) 26 | { 27 | if (failure.CustomState == null) 28 | { 29 | failure.CustomState = context.InstanceToValidate.Id; 30 | } 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Features/MasterFiles/DisposalReasonDto.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Application.Common.Mappings; 2 | using ApiTemplate.Api.Application.Common.Validation; 3 | using ApiTemplate.Api.Domain.Model.MasterFiles; 4 | using FluentValidation; 5 | 6 | namespace ApiTemplate.Api.Application.Features.MasterFiles 7 | { 8 | public class DisposalReasonDto : MasterFileDto, IMapToAndFrom, IRequireValidation 9 | { 10 | public virtual string Reason { get; set; } 11 | public virtual string DisposalReasonDescription { get; set; } 12 | } 13 | 14 | public class DisposalReasonDtoValidator : PrimaryKeyValidator 15 | { 16 | public DisposalReasonDtoValidator() 17 | { 18 | RuleFor(p => p.Reason) 19 | .NotEmpty() 20 | .MaximumLength(100); 21 | 22 | RuleFor(x => x.DisposalReasonDescription) 23 | .MaximumLength(500); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Features/MasterFiles/MasterFileDto.cs: -------------------------------------------------------------------------------- 1 | namespace ApiTemplate.Api.Application.Features.MasterFiles 2 | { 3 | public class MasterFileDto 4 | { 5 | public virtual int Id { get; set; } 6 | public virtual bool ActiveFlag { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Features/MasterFiles/WeatherConditionDto.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Application.Common.Mappings; 2 | using ApiTemplate.Api.Application.Common.Validation; 3 | using ApiTemplate.Api.Domain.Model.MasterFiles; 4 | using FluentValidation; 5 | using Newtonsoft.Json; 6 | 7 | namespace ApiTemplate.Api.Application.Features.MasterFiles 8 | { 9 | public class WeatherConditionDto : MasterFileDto, IMapToAndFrom, IRequireValidation 10 | { 11 | public string Condition { get; set; } 12 | 13 | [JsonProperty] 14 | public WeatherTypeDto WeatherType { get; set; } 15 | } 16 | 17 | public class WeatherConditionDtoValidator : PrimaryKeyValidator 18 | { 19 | public WeatherConditionDtoValidator() 20 | { 21 | RuleFor(p => p.Condition) 22 | .NotEmpty() 23 | .MaximumLength(200); 24 | 25 | // RuleFor(p => p.WeatherTypeId).NotEmpty(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Features/MasterFiles/WeatherTypeDto.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Application.Common.Mappings; 2 | using ApiTemplate.Api.Application.Common.Validation; 3 | using ApiTemplate.Api.Domain.Model.MasterFiles; 4 | using FluentValidation; 5 | 6 | namespace ApiTemplate.Api.Application.Features.MasterFiles 7 | { 8 | public class WeatherTypeDto : MasterFileDto, IMapToAndFrom, IRequireValidation 9 | { 10 | public virtual string WeatherTypeName { get; set; } 11 | public virtual bool RequiredFlag { get; set; } 12 | } 13 | 14 | public class WeatherTypeDtoValidator : PrimaryKeyValidator 15 | { 16 | public WeatherTypeDtoValidator() 17 | { 18 | RuleFor(p => p.WeatherTypeName) 19 | .NotEmpty() 20 | .MaximumLength(200); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Features/ToDoItems/DeleteTodoItemCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using ApiTemplate.Api.Application.Common.Interfaces; 4 | using ApiTemplate.Api.Domain.Common.FluentResult; 5 | using FluentResults; 6 | using MediatR; 7 | 8 | namespace ApiTemplate.Api.Application.Features.ToDoItems 9 | { 10 | public class DeleteTodoItemCommand : IRequest 11 | { 12 | public int Id { get; set; } 13 | } 14 | 15 | public class DeleteTodoItemCommandHandler : IRequestHandler 16 | { 17 | private readonly IUnitOfWork _context; 18 | 19 | public DeleteTodoItemCommandHandler(IUnitOfWork context) 20 | { 21 | _context = context; 22 | } 23 | 24 | public async Task Handle(DeleteTodoItemCommand request, CancellationToken cancellationToken) 25 | { 26 | var entity = await _context.ToDoItems.FindAsync(request.Id); 27 | 28 | if (entity == null) 29 | { 30 | return Resultz.RecordNotFound("Id", request.Id); 31 | } 32 | 33 | _context.ToDoItems.Remove(entity); 34 | 35 | await _context.SaveChangesAsync(cancellationToken); 36 | 37 | return Result.Ok(); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Features/ToDoItems/EmailValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ApiTemplate.Api.Domain.Model.ToDos; 3 | using FluentValidation.Resources; 4 | using FluentValidation.Validators; 5 | 6 | namespace ApiTemplate.Api.Application.Features.ToDoItems 7 | { 8 | public class EmailValidator : PropertyValidator 9 | { 10 | public EmailValidator(IStringSource errorMessageSource) : base(errorMessageSource) 11 | { 12 | } 13 | 14 | public EmailValidator(string errorMessage) : base(errorMessage) 15 | { 16 | } 17 | 18 | protected override bool IsValid(PropertyValidatorContext context) 19 | { 20 | var result = Email.Create(context.PropertyValue.ToString()); 21 | return result.IsSuccess; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Features/ToDoItems/GetAllToDosQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using ApiTemplate.Api.Application.Common.Interfaces; 6 | using ApiTemplate.Api.Domain.Model.ToDos; 7 | using AutoMapper; 8 | using AutoMapper.QueryableExtensions; 9 | using MediatR; 10 | using Microsoft.EntityFrameworkCore; 11 | 12 | namespace ApiTemplate.Api.Application.Features.ToDoItems 13 | { 14 | public class GetAllToDosQuery : IRequest> 15 | { 16 | } 17 | 18 | public class GetAllToDosQueryHandler : IRequestHandler> 19 | { 20 | private readonly IQueryDb _db; 21 | private readonly IMapper _mapper; 22 | 23 | public GetAllToDosQueryHandler(IQueryDb db, IMapper mapper) 24 | { 25 | _db = db; 26 | _mapper = mapper; 27 | } 28 | 29 | public async Task> Handle(GetAllToDosQuery request, CancellationToken cancellationToken) 30 | { 31 | var items = await _db.QueryFor() 32 | .ProjectTo(_mapper.ConfigurationProvider) 33 | .OrderBy(t => t.Title) 34 | .ToListAsync(cancellationToken); 35 | 36 | return items; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Features/ToDoItems/ToDoItemDetailDto.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Application.Common.Mappings; 2 | using ApiTemplate.Api.Domain.Model.ToDos; 3 | 4 | namespace ApiTemplate.Api.Application.Features.ToDoItems 5 | { 6 | public class ToDoItemDetailDto : IMapFrom 7 | { 8 | public int Id { get; set; } 9 | 10 | public string Title { get; set; } 11 | 12 | public string Description { get; set; } 13 | 14 | public string Email { get; set; } 15 | 16 | public bool IsDone { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Application/Features/ToDoItems/ToDoItemDto.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Application.Common.Mappings; 2 | using ApiTemplate.Api.Domain.Model.ToDos; 3 | using AutoMapper; 4 | 5 | namespace ApiTemplate.Api.Application.Features.ToDoItems 6 | { 7 | public class ToDoItemDto : IMapFrom 8 | { 9 | public int Id { get; set; } 10 | 11 | public string Title { get; set; } 12 | 13 | public string Description { get; set; } 14 | 15 | public string Email { get; set; } 16 | 17 | public bool Done { get; set; } 18 | 19 | public void Mapping(Profile profile) 20 | { 21 | profile.CreateMap() 22 | .ForMember(d => d.Done, 23 | opt => opt.MapFrom(s => s.IsDone)); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Common/ActionResults.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Contracts.Responses; 2 | using ApiTemplate.Api.Domain.Common.FluentResult; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace ApiTemplate.Api.Common 6 | { 7 | public static class ActionResults 8 | { 9 | public static IActionResult ValidationFailure(string propertyId, string validationMessage) 10 | { 11 | var validationError = Resultz.Error(propertyId, validationMessage); 12 | return new BadRequestObjectResult(new ErrorResponse(validationError)); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Common/HostingEnvironmentExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace ApiTemplate.Api.Common 5 | { 6 | public static class HostingEnvironmentExtensions 7 | { 8 | public static bool IsTest(this IHostEnvironment environment) 9 | { 10 | return environment.IsEnvironment("Test") || Environment.GetEnvironmentVariable("NCrunch") == "1"; // || environment.IsEnvironment("TFS"); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Configuration/Options/IdentityServerOptions.cs: -------------------------------------------------------------------------------- 1 | namespace ApiTemplate.Api.Configuration.Options 2 | { 3 | public class IdentityServerOptions 4 | { 5 | public string IdentityServerUrl { get; set; } 6 | public bool RequiresHttps { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Contracts/Responses/RecordsCreatedResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ApiTemplate.Api.Domain.Common.FluentResult; 3 | 4 | namespace ApiTemplate.Api.Contracts.Responses 5 | { 6 | public class RecordsCreatedResponse 7 | { 8 | public RecordsCreatedResponse() { } 9 | 10 | public RecordsCreatedResponse(RecordsCreatedSuccess reason) 11 | { 12 | NewIds = reason.NewIds; 13 | Message = reason.Message; 14 | } 15 | 16 | public RecordsCreatedResponse(int newId) 17 | { 18 | NewIds.Add(newId); 19 | Message = $"Record created with Id {newId}"; 20 | } 21 | 22 | public RecordsCreatedResponse(List newIds) 23 | { 24 | NewIds = newIds; 25 | Message = "Records created"; 26 | } 27 | 28 | public List NewIds { get; set; } = new List(); 29 | public string Message { get; set; } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Contracts/Responses/SuccessResponse.cs: -------------------------------------------------------------------------------- 1 | namespace ApiTemplate.Api.Contracts.Responses 2 | { 3 | public class SuccessResponse 4 | { 5 | public string Message { get; set; } 6 | public T Data { get; set; } 7 | public SuccessResponse() { } 8 | 9 | public SuccessResponse(T response, string message = "Success") 10 | { 11 | Message = message; 12 | Data = response; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Controllers/ApiController.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace ApiTemplate.Api.Controllers 6 | { 7 | [ApiController] 8 | [Route("[controller]")] 9 | [Produces("application/json")] 10 | public abstract class ApiController : ControllerBase 11 | { 12 | private IMediator _mediator; 13 | protected IMediator Mediator => _mediator ??= HttpContext.RequestServices.GetService(); 14 | } 15 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Controllers/InfoController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Configuration; 3 | 4 | namespace ApiTemplate.Api.Controllers 5 | { 6 | /// 7 | /// temporary controller to provide feedback while building out-of-process component tests 8 | /// 9 | public class InfoController : ApiController 10 | { 11 | private readonly IConfiguration _configuration; 12 | 13 | public InfoController(IConfiguration configuration) 14 | { 15 | _configuration = configuration; 16 | } 17 | 18 | [HttpGet] 19 | public string Get() 20 | { 21 | return _configuration.GetConnectionString("AppDb"); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Controllers/MasterFiles/MasterFileControllers.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Application.Features.MasterFiles; 2 | using ApiTemplate.Api.Domain.Model.MasterFiles; 3 | 4 | namespace ApiTemplate.Api.Controllers.MasterFiles 5 | { 6 | public class DisposalReasonController : MasterFileControllerFor { } 7 | public class WeatherTypeController : MasterFileControllerFor { } 8 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Common/FluentResult/AppError.cs: -------------------------------------------------------------------------------- 1 | using FluentResults; 2 | 3 | namespace ApiTemplate.Api.Domain.Common.FluentResult 4 | { 5 | // Base error for all application errors. 6 | public class AppError : Error, IFailure 7 | { 8 | public string PropertyName { get; } 9 | public int RowKey { get; } 10 | 11 | public AppError(string propertyName, string message, int rowKey = int.MinValue) 12 | : this(message, rowKey) 13 | { 14 | PropertyName = propertyName; 15 | } 16 | 17 | public AppError(string message, int rowKey = int.MinValue) 18 | { 19 | RowKey = rowKey; 20 | Message = message; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Common/FluentResult/AppWarning.cs: -------------------------------------------------------------------------------- 1 | namespace ApiTemplate.Api.Domain.Common.FluentResult 2 | { 3 | // Base warning for all application warnings. 4 | public class AppWarning : Warning, IFailure 5 | { 6 | public string PropertyName { get; } 7 | public int RowKey { get; } 8 | 9 | public AppWarning(string propertyName, string message, int rowKey = int.MinValue) 10 | : this(message, rowKey) 11 | { 12 | PropertyName = propertyName; 13 | } 14 | 15 | public AppWarning(string message, int rowKey = int.MinValue) 16 | : base(message) 17 | { 18 | RowKey = rowKey; 19 | Message = message; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Common/FluentResult/IFailure.cs: -------------------------------------------------------------------------------- 1 | namespace ApiTemplate.Api.Domain.Common.FluentResult 2 | { 3 | public interface IFailure 4 | { 5 | string PropertyName { get; } 6 | int RowKey { get; } 7 | string Message { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Common/FluentResult/RecordsCreatedSuccess.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using FluentResults; 3 | 4 | namespace ApiTemplate.Api.Domain.Common.FluentResult 5 | { 6 | public class RecordsCreatedSuccess : Success 7 | { 8 | public List NewIds { get; } 9 | 10 | public RecordsCreatedSuccess(int id) 11 | : base("Record created") 12 | { 13 | NewIds = new List {id}; 14 | } 15 | 16 | public RecordsCreatedSuccess(List newIds) 17 | : base("Records created") 18 | { 19 | NewIds = newIds; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Common/FluentResult/RecordsNotFoundAppError.cs: -------------------------------------------------------------------------------- 1 | namespace ApiTemplate.Api.Domain.Common.FluentResult 2 | { 3 | public class RecordsNotFoundAppError : AppError 4 | { 5 | public RecordsNotFoundAppError(string propertyName, int id) 6 | : base(propertyName, $"Record '{id}' not found", rowKey: id) 7 | { 8 | } 9 | 10 | public RecordsNotFoundAppError() 11 | : base("Records not found") 12 | { 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Common/FluentResult/TreatWarningsAsErrors.cs: -------------------------------------------------------------------------------- 1 | using FluentResults; 2 | 3 | namespace ApiTemplate.Api.Domain.Common.FluentResult 4 | { 5 | public class TreatWarningsAsErrors : Error 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Common/FluentResult/ValidationMessages.cs: -------------------------------------------------------------------------------- 1 | namespace ApiTemplate.Api.Domain.Common.FluentResult 2 | { 3 | public class ValidationMessages 4 | { 5 | public const string MustBeValidPostCode = " must be a valid post code."; 6 | public const string DoesNotExist = " does not exist."; 7 | public const string IsRequired = " is required."; 8 | public const string CannotBeInPast = " cannot be in the past."; 9 | public const string IsInvalid = " is invalid."; 10 | public const string MustBeGreaterThanZero = " must be > 0."; 11 | public const string MustBePositive = " must be a positive number."; 12 | public const string AlreadyAssociatedWithAnotherParent = " is already asociated with another parent."; 13 | 14 | public static string IsRequiredFor(string propertyName) 15 | { 16 | return $"'{propertyName}'{IsRequired}"; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Common/FluentResult/ValidationSeverity.cs: -------------------------------------------------------------------------------- 1 | namespace ApiTemplate.Api.Domain.Common.FluentResult 2 | { 3 | //[Flags] 4 | public enum ValidationSeverity 5 | { 6 | Error, 7 | Warning 8 | } 9 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Common/FluentResult/Warning.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using FluentResults; 3 | 4 | namespace ApiTemplate.Api.Domain.Common.FluentResult 5 | { 6 | public class Warning : Reason 7 | { 8 | public Warning(string message) 9 | { 10 | Message = message; 11 | } 12 | 13 | public Warning WithMetadata(string metadataName, object metadataValue) 14 | { 15 | Metadata.Add(metadataName, metadataValue); 16 | return this; 17 | } 18 | 19 | public Warning WithMetadata(Dictionary metadata) 20 | { 21 | foreach (var metadataItem in metadata) 22 | { 23 | Metadata.Add(metadataItem.Key, metadataItem.Value); 24 | } 25 | 26 | return this; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Common/MasterFile.cs: -------------------------------------------------------------------------------- 1 | namespace ApiTemplate.Api.Domain.Common 2 | { 3 | public abstract class MasterFile : Entity 4 | { 5 | public virtual bool ActiveFlag { get; protected set; } 6 | 7 | public virtual void MakeActive() 8 | { 9 | ActiveFlag = true; 10 | } 11 | 12 | public virtual void MakeInactive() 13 | { 14 | ActiveFlag = false; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Common/SystemTime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ApiTemplate.Api.Domain.Common 4 | { 5 | public static class SystemTime 6 | { 7 | public static Func UtcNow = () => DateTime.UtcNow; 8 | 9 | public static void Reset() 10 | { 11 | UtcNow = () => DateTime.UtcNow; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Model/MasterFiles/DisposalReason.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Domain.Common; 2 | 3 | namespace ApiTemplate.Api.Domain.Model.MasterFiles 4 | { 5 | public class DisposalReason : MasterFile 6 | { 7 | public string Reason { get; protected set; } 8 | 9 | public string DisposalReasonDescription { get; protected set; } 10 | 11 | protected DisposalReason() { } 12 | 13 | public DisposalReason(string reason, string disposalReasonDescription) 14 | { 15 | Reason = reason; 16 | DisposalReasonDescription = disposalReasonDescription; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Model/MasterFiles/WeatherCondition.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Domain.Common; 2 | 3 | namespace ApiTemplate.Api.Domain.Model.MasterFiles 4 | { 5 | public class WeatherCondition : MasterFile 6 | { 7 | public string Condition { get; protected set; } 8 | public virtual WeatherType WeatherType { get; set; } 9 | protected WeatherCondition() { } 10 | 11 | public WeatherCondition(string condition, WeatherType weatherType) 12 | { 13 | Condition = condition; 14 | WeatherType = weatherType; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Model/MasterFiles/WeatherType.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Domain.Common; 2 | 3 | namespace ApiTemplate.Api.Domain.Model.MasterFiles 4 | { 5 | public class WeatherType : MasterFile 6 | { 7 | public string WeatherTypeName { get; protected set; } 8 | public bool RequiredFlag { get; protected set; } 9 | protected WeatherType() { } 10 | 11 | public WeatherType(string weatherTypeName, bool requiredFlag) 12 | { 13 | WeatherTypeName = weatherTypeName; 14 | RequiredFlag = requiredFlag; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Model/SharedValueObjects/PositiveDecimal.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ApiTemplate.Api.Domain.Common; 3 | using FluentResults; 4 | 5 | namespace ApiTemplate.Api.Domain.Model.SharedValueObjects 6 | { 7 | public class PositiveDecimal : ValueObject 8 | { 9 | public decimal Value { get; } 10 | 11 | private PositiveDecimal(decimal value) 12 | { 13 | Value = value; 14 | } 15 | 16 | public static Result Create(decimal value) 17 | { 18 | if (value <= 0) 19 | return Results.Fail("Value must greater than zero."); 20 | 21 | return Results.Ok(new PositiveDecimal(value)); 22 | } 23 | 24 | public static Result CreateNullable(decimal? value) 25 | { 26 | if (value == null) 27 | return Results.Ok((PositiveDecimal?)null); 28 | 29 | return Create((decimal)value); 30 | } 31 | 32 | protected override IEnumerable GetEqualityComponents() 33 | { 34 | yield return Value; 35 | } 36 | 37 | public static implicit operator decimal(PositiveDecimal value) 38 | { 39 | return value.Value; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Model/SharedValueObjects/PositiveInteger.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ApiTemplate.Api.Domain.Common; 3 | using FluentResults; 4 | 5 | namespace ApiTemplate.Api.Domain.Model.SharedValueObjects 6 | { 7 | public class PositiveInteger : ValueObject 8 | { 9 | public int Value { get; } 10 | 11 | private PositiveInteger(int value) 12 | { 13 | Value = value; 14 | } 15 | 16 | public static Result Create(int? value) 17 | { 18 | if (value == null) 19 | return Results.Fail("Value cannot be empty."); 20 | 21 | if (value <= 0) 22 | return Results.Fail("Value must greater than zero."); 23 | 24 | return Results.Ok(new PositiveInteger((int)value)); 25 | } 26 | 27 | public static Result CreateNullable(int? value) 28 | { 29 | if (value == null) 30 | return Results.Ok((PositiveInteger?)null); 31 | 32 | return Create(value); 33 | } 34 | 35 | protected override IEnumerable GetEqualityComponents() 36 | { 37 | yield return Value; 38 | } 39 | 40 | public static implicit operator int(PositiveInteger value) 41 | { 42 | return value.Value; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Model/ToDos/Email.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.RegularExpressions; 3 | using ApiTemplate.Api.Domain.Common; 4 | using FluentResults; 5 | 6 | namespace ApiTemplate.Api.Domain.Model.ToDos 7 | { 8 | public class Email : ValueObject 9 | { 10 | public string Value { get; } 11 | 12 | private Email(string value) 13 | { 14 | Value = value; 15 | } 16 | 17 | public static Result Create(string email) 18 | { 19 | if (string.IsNullOrWhiteSpace(email)) 20 | return Results.Fail("Email should not be empty"); 21 | 22 | email = email.Trim(); 23 | 24 | if (email.Length > 200) 25 | return Results.Fail("Email is too long"); 26 | 27 | if (!Regex.IsMatch(email, @"^(.+)@(.+)$")) 28 | return Results.Fail("Email is invalid"); 29 | 30 | return Results.Ok(new Email(email)); 31 | } 32 | 33 | protected override IEnumerable GetEqualityComponents() 34 | { 35 | yield return Value; 36 | } 37 | 38 | public static implicit operator string(Email email) 39 | { 40 | return email.Value; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Model/ToDos/ToDoItem.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Domain.Common; 2 | 3 | namespace ApiTemplate.Api.Domain.Model.ToDos 4 | { 5 | public class ToDoItem : Entity 6 | { 7 | public ToDoItem(string title, string description, Email email) 8 | { 9 | Title = title; 10 | Description = description; 11 | Email = email; 12 | } 13 | 14 | public ToDoItem(int id, string title, string description, Email email) 15 | : this(title, description, email) 16 | { 17 | Id = id; 18 | } 19 | 20 | public ToDoItem() { } 21 | 22 | public string Title { get; private set; } = string.Empty; 23 | public string Description { get; private set; } 24 | public Email Email { get; private set; } 25 | public bool IsDone { get; private set; } 26 | 27 | public void SetCompleted() 28 | { 29 | IsDone = true; 30 | } 31 | 32 | public void Update(string title, string description, Email email, bool isDone) 33 | { 34 | Title = title; 35 | Description = description; 36 | Email = email; 37 | IsDone = isDone; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Utils/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace ApiTemplate.Api.Domain.Utils 6 | { 7 | public static class EnumerableExtensions 8 | { 9 | public static IEnumerable Each(this IEnumerable enumerable, Action action) 10 | { 11 | var items = enumerable as T[] ?? enumerable.ToArray(); 12 | foreach (var item in items) 13 | { 14 | action(item); 15 | } 16 | 17 | return items; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Domain/Utils/ExceptionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace ApiTemplate.Api.Domain.Utils 5 | { 6 | public static class ExceptionExtensions 7 | { 8 | public static string UnwrapInnerExceptionMessages(this Exception exception) 9 | { 10 | var sb = new StringBuilder(); 11 | 12 | if (!string.IsNullOrWhiteSpace(exception.Message)) 13 | { 14 | sb.AppendLine(exception.Message); 15 | } 16 | 17 | if (exception.InnerException != null) 18 | { 19 | sb = GetInnerExceptionMessage(sb, exception.InnerException); 20 | } 21 | 22 | return sb.ToString(); 23 | } 24 | 25 | private static StringBuilder GetInnerExceptionMessage(StringBuilder sb, Exception exception) 26 | { 27 | if (!string.IsNullOrWhiteSpace(exception.Message)) 28 | { 29 | sb.AppendLine(exception.Message); 30 | } 31 | 32 | if (exception.InnerException != null) 33 | { 34 | sb = GetInnerExceptionMessage(sb, exception.InnerException); 35 | } 36 | 37 | return sb; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Infrastructure/HealthChecks/DependencyHealthCheck.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ApiTemplate.Api.Infrastructure.HealthChecks 5 | { 6 | public class DependencyHealthCheck 7 | { 8 | public string Dependency { get; set; } 9 | public string Status { get; set; } 10 | 11 | public string Duration { get; set; } 12 | 13 | public Exception Exception { get; set; } 14 | 15 | public IReadOnlyDictionary Data { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Infrastructure/HealthChecks/LiveHealthCheck.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Diagnostics.HealthChecks; 2 | 3 | namespace ApiTemplate.Api.Infrastructure.HealthChecks 4 | { 5 | public class LiveHealthCheck 6 | { 7 | public string OverallStatus { get; set; } 8 | 9 | public string TotalChecksDuration { get; set; } 10 | 11 | public static LiveHealthCheck Create(HealthReport result) 12 | { 13 | return new LiveHealthCheck 14 | { 15 | OverallStatus = result.Status.ToString(), 16 | TotalChecksDuration = result.TotalDuration.TotalSeconds.ToString("0:0.00"), 17 | }; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Infrastructure/HealthChecks/ReadyHealthCheck.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.Extensions.Diagnostics.HealthChecks; 4 | 5 | namespace ApiTemplate.Api.Infrastructure.HealthChecks 6 | { 7 | public class ReadyHealthCheck 8 | { 9 | public string OverallStatus { get; set; } 10 | 11 | public string TotalChecksDuration { get; set; } 12 | 13 | public List DependencyHealthChecks { get; set; } = new List(); 14 | 15 | public static ReadyHealthCheck Create(HealthReport result) 16 | { 17 | return new ReadyHealthCheck 18 | { 19 | OverallStatus = result.Status.ToString(), 20 | TotalChecksDuration = result.TotalDuration.TotalSeconds.ToString("0:0.00"), 21 | DependencyHealthChecks = Enumerable.ToList(result.Entries 22 | .Select(entry => new DependencyHealthCheck 23 | { 24 | Dependency = entry.Key, 25 | Status = entry.Value.Status.ToString(), 26 | Duration = entry.Value.Duration.TotalSeconds.ToString("0:0.00"), 27 | Exception = entry.Value.Exception, 28 | Data = entry.Value.Data 29 | })) 30 | }; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Infrastructure/Identity/CurrentUserService.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using ApiTemplate.Api.Application.Common.Interfaces; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace ApiTemplate.Api.Infrastructure.Identity 6 | { 7 | public class CurrentUserService : ICurrentUserService 8 | { 9 | private const string UserIdClaim = "UserID"; 10 | 11 | public CurrentUserService(IHttpContextAccessor httpContextAccessor) 12 | { 13 | var userId = httpContextAccessor.HttpContext?.User?.FindFirstValue(UserIdClaim); 14 | if(!string.IsNullOrEmpty(userId)) 15 | { 16 | UserId = int.Parse(userId); 17 | } 18 | } 19 | 20 | public int UserId { get; } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Infrastructure/Logging/SerilogApplicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Serilog; 3 | 4 | namespace ApiTemplate.Api.Infrastructure.Logging 5 | { 6 | public static class SerilogApplicationBuilderExtensions 7 | { 8 | public static IApplicationBuilder UseSerilog(this IApplicationBuilder app) 9 | { 10 | return app.UseSerilogRequestLogging(opts 11 | => opts.EnrichDiagnosticContext = LoggingHelper.EnrichFromRequest); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Infrastructure/Logging/SerilogLoggingActionFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Filters; 2 | using Serilog; 3 | 4 | namespace ApiTemplate.Api.Infrastructure.Logging 5 | { 6 | // MVC-specific features like the action method ID, RazorPages Handler name, 7 | // or the ModelValidationState are only available in an MVC context, 8 | // so can't be directly accessed by Serilog's middleware. 9 | public class SerilogLoggingActionFilter : IActionFilter 10 | { 11 | private readonly IDiagnosticContext _diagnosticContext; 12 | public SerilogLoggingActionFilter(IDiagnosticContext diagnosticContext) 13 | { 14 | _diagnosticContext = diagnosticContext; 15 | } 16 | 17 | public void OnActionExecuting(ActionExecutingContext context) 18 | { 19 | _diagnosticContext.Set("RouteData", context.ActionDescriptor.RouteValues); 20 | _diagnosticContext.Set("ActionName", context.ActionDescriptor.DisplayName); 21 | _diagnosticContext.Set("ActionId", context.ActionDescriptor.Id); 22 | _diagnosticContext.Set("ValidationState", context.ModelState.IsValid); 23 | } 24 | 25 | // Required by the interface 26 | public void OnActionExecuted(ActionExecutedContext context) { } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Infrastructure/Persistence/Configurations/DisposalReasonConfiguration.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Domain.Model.MasterFiles; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | 5 | namespace ApiTemplate.Api.Infrastructure.Persistence.Configurations 6 | { 7 | public class DisposalReasonConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.ToTable("sw_Disposal_ReasonT") 12 | .HasIndex(e => e.Reason) 13 | .IsUnique(); 14 | 15 | builder.Property(e => e.Id) 16 | .HasColumnName("DisposalReasonID"); 17 | 18 | builder.Property(e => e.ActiveFlag) 19 | .IsRequired() 20 | .HasDefaultValueSql("((1))"); 21 | 22 | builder.Property(t => t.Reason) 23 | .HasColumnName("DisposalReason") 24 | .HasMaxLength(100) 25 | .IsUnicode(false); 26 | 27 | builder.Property(e => e.DisposalReasonDescription) 28 | .HasMaxLength(500) 29 | .IsUnicode(false); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Infrastructure/Persistence/Configurations/TodoItemConfiguration.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Domain.Model.ToDos; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | 5 | namespace ApiTemplate.Api.Infrastructure.Persistence.Configurations 6 | { 7 | public class TodoItemConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.Property(t => t.Title) 12 | .HasMaxLength(200) 13 | .IsRequired(); 14 | 15 | builder.Property(t => t.Description) 16 | .HasMaxLength(400) 17 | .IsRequired(); 18 | 19 | // Use EF value conversions for single-property value objects 20 | builder.Property(t => t.Email) 21 | .HasConversion(p => p.Value, p => Email.Create(p).Value) 22 | .HasMaxLength(250); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Infrastructure/Persistence/Configurations/WeatherConditionConfiguration.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Domain.Model.MasterFiles; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | 5 | namespace ApiTemplate.Api.Infrastructure.Persistence.Configurations 6 | { 7 | public class WeatherConditionConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.ToTable("sw_WeatherT"); 12 | 13 | builder.Property(e => e.Id) 14 | .HasColumnName("WeatherID"); 15 | 16 | builder.Property(t => t.Condition) 17 | .HasColumnName("WeatherCondition") 18 | .HasMaxLength(200) 19 | .IsRequired(); 20 | 21 | builder 22 | .HasOne(p => p.WeatherType) 23 | .WithMany() 24 | .HasForeignKey("WeatherTypeID") 25 | .HasConstraintName("FK_sw_WeatherT_sw_Weather_TypeT"); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Infrastructure/Persistence/Configurations/WeatherTypeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Domain.Model.MasterFiles; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | 5 | namespace ApiTemplate.Api.Infrastructure.Persistence.Configurations 6 | { 7 | public class WeatherTypeConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.ToTable("sw_Weather_TypeT"); 12 | 13 | builder.Property(e => e.Id) 14 | .HasColumnName("WeatherTypeID"); 15 | 16 | builder.Property(t => t.WeatherTypeName) 17 | .HasColumnName("WeatherType") 18 | .HasMaxLength(200) 19 | .IsRequired(); 20 | 21 | builder.Property(t => t.RequiredFlag) 22 | .HasDefaultValue(false) 23 | .IsRequired(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Infrastructure/Persistence/QueryDbContext.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using ApiTemplate.Api.Application.Common.Interfaces; 3 | using ApiTemplate.Api.Domain.Common; 4 | using ApiTemplate.Api.Domain.Model.ToDos; 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | namespace ApiTemplate.Api.Infrastructure.Persistence 8 | { 9 | public class QueryDbContext : IQueryDb 10 | { 11 | private readonly AppDbContext _db; 12 | 13 | public QueryDbContext(AppDbContext db) 14 | { 15 | _db = db; 16 | db.ChangeTracker.AutoDetectChangesEnabled = false; // is this needed with QueryTrackingBehavior? 17 | db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; 18 | } 19 | 20 | public IQueryable ToDoItems => _db.ToDoItems; 21 | 22 | // This is a generic alternative to above. Team should discuss preference. 23 | public IQueryable QueryFor() where T : Entity 24 | { 25 | return _db.Set(); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:31674/", 7 | "sslPort": 44340 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Api": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/README.md: -------------------------------------------------------------------------------- 1 | # Presentation Layer - API 2 | 3 | This layer contains the ASP.Net Core Web API artifacts and is responsible for converting the command/query results into HTTP action results with HTTP status codes, etc. 4 | 5 | # Features 6 | 7 | ## Health checks 8 | See [Microsoft docs](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/monitor-app-health) for purpose and benefits of real-time information about the state of your containers and microservices and the dependencies they rely on. 9 | 10 | Navigate to the following URLs: 11 | * /health/live: liveness check - a fast check of whether app is alive or dead. 12 | * /health/ready: readiness check - slightly slower check that also pings app's dependencies, such as SQL Server or messaging. 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/SwaggerExamples/Requests/CreateTodoItemCommandExample.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Application.Features.ToDoItems; 2 | using Swashbuckle.AspNetCore.Filters; 3 | 4 | namespace ApiTemplate.Api.SwaggerExamples.Requests 5 | { 6 | public class CreateTodoItemCommandExample : IExamplesProvider 7 | { 8 | public CreateTodoItemCommand GetExamples() 9 | { 10 | return new CreateTodoItemCommand 11 | { 12 | Title = "Run and Review Tests", 13 | Description = "Make sure all the tests run and review what they are doing.", 14 | Email = "tests are important" 15 | }; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/SwaggerExamples/Responses/RecordCreatedResponseExample.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Contracts.Responses; 2 | using Swashbuckle.AspNetCore.Filters; 3 | 4 | namespace ApiTemplate.Api.SwaggerExamples.Responses 5 | { 6 | public class RecordsCreatedResponseExample : IExamplesProvider 7 | { 8 | public RecordsCreatedResponse GetExamples() 9 | { 10 | return new RecordsCreatedResponse(39); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/SwaggerExamples/Responses/ToDoItemDtoExample.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Application.Features.ToDoItems; 2 | using Swashbuckle.AspNetCore.Filters; 3 | 4 | namespace ApiTemplate.Api.SwaggerExamples.Responses 5 | { 6 | public class ToDoItemDtoExample : IExamplesProvider 7 | { 8 | public ToDoItemDto GetExamples() 9 | { 10 | return new ToDoItemDto 11 | { 12 | Id = 39, 13 | Title = "Run and Review Tests", 14 | Description = "Make sure all the tests run and review what they are doing.", 15 | Email = "tests are important", 16 | Done = true 17 | }; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/appsettings.Test.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "AppDb": "Server=.;Database=AspNetCoreApiDb-Test;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | }, 5 | "TestExecutionMode": "InProcess" 6 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/app/Api/appsettings.ncrunch.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "AppDb": "Server=.;Database=AspNetCoreApiDb-Test-NCrunch;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | }, 5 | "TestExecutionMode": "InProcess" 6 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/Application/Aspects/DevOpsStory.cs: -------------------------------------------------------------------------------- 1 | using Specify.Stories; 2 | 3 | namespace Specs.Acceptance.Application.Aspects 4 | { 5 | public class DevOpsStory : UserStory 6 | { 7 | public DevOpsStory() 8 | { 9 | AsA = "As a DevOps person"; 10 | IWant = "I want to check the status of the application"; 11 | SoThat = "So that I can operate and monitor the software for the users."; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/Application/Aspects/HealthyLivenessCheck.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using ApiTemplate.Api.Infrastructure.HealthChecks; 4 | using FluentAssertions; 5 | using Specs.Library.Drivers.Api; 6 | 7 | namespace Specs.Acceptance.Application.Aspects 8 | { 9 | public class HealthyLivenessCheck : ScenarioFor 10 | { 11 | private ApiResponse _result; 12 | 13 | public void Given_the_application_is_running() 14 | { 15 | } 16 | 17 | public async Task When_I_check_the_liveness_health() 18 | { 19 | _result = await SUT.GetAsync("health/live"); 20 | } 21 | 22 | public void Then_the_status_should_be_healthy() 23 | { 24 | _result.Model.OverallStatus.Should().Be("Healthy"); 25 | } 26 | 27 | public void AndThen_the_status_code_should_be_Ok() 28 | { 29 | _result.StatusCode.Should().Be(HttpStatusCode.OK); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/Application/Aspects/HealthyReadinessCheck.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using ApiTemplate.Api.Infrastructure.HealthChecks; 4 | using FluentAssertions; 5 | using Specs.Library.Drivers.Api; 6 | 7 | namespace Specs.Acceptance.Application.Aspects 8 | { 9 | public class HealthyReadinessCheck : ScenarioFor 10 | { 11 | private ApiResponse _result; 12 | 13 | public void Given_the_database_is_running() 14 | { 15 | } 16 | 17 | public async Task When_I_check_the_readiness() 18 | { 19 | _result = await SUT.GetAsync("health/ready"); 20 | } 21 | 22 | public void Then_the_status_should_be_healthy() 23 | { 24 | _result.Model.OverallStatus.Should().Be("Healthy"); 25 | } 26 | 27 | public void AndThen_the_status_code_should_be_Ok() 28 | { 29 | _result.StatusCode.Should().Be(HttpStatusCode.OK); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/Application/Features/MasterFiles/ReadMe.md: -------------------------------------------------------------------------------- 1 | This is where master file specs will live after the sprint. 2 | For now, they are in the Current Sprint folder, which has all the specs for this sprint. -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/Application/ReadMe.md: -------------------------------------------------------------------------------- 1 | This is the home of the acceptance specs. 2 | It is named 'Application' folder because it corresponds with the Application project, which is where application 'Features' are implemented. 3 | New specs will start off in the '_CurrentSprint' folder and then move here at the end of the sprint. 4 | This is the 'living documentation' and so is organised in whatever way creates the most useful and readable documentation. 5 | The audience for this documentation is the customer and whole team and so the specs are written in the ubiquitous language and are hopefully understood by everyone. -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/README.md: -------------------------------------------------------------------------------- 1 | # Acceptance Specifications 2 | 3 | The acceptance specs are the component tests in Martin Fowler's [microservices testing strategy](https://martinfowler.com/articles/microservice-testing/#testing-component-introduction). 4 | 5 | The in-process component tests run in the private build and build pipeline and the same tests can run out-of-process against deployed services in the release pipeline. -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/ScenarioFor.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Specify.Stories; 3 | using Specs.Library.Data; 4 | using Specs.Library.Helpers; 5 | 6 | namespace Specs.Acceptance 7 | { 8 | [TestFixture] 9 | public abstract class ScenarioFor : Specify.ScenarioFor 10 | where TSut : class 11 | where TStory : Story, new() 12 | { 13 | protected IDb Db => Container.Get(); 14 | 15 | [ExecuteScenario] 16 | public override void Specify() 17 | { 18 | base.Specify(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_CurrentSprint/MasterFiles/DisposalReasons/DeleteOne/DeleteExistingDisposalReason.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using ApiTemplate.Api.Application.Features.MasterFiles; 5 | using ApiTemplate.Api.Contracts; 6 | using ApiTemplate.Api.Domain.Model.MasterFiles; 7 | using FluentAssertions; 8 | using Specs.Library.Builders.Entities.MasterFiles; 9 | using Specs.Library.Data; 10 | using Specs.Library.Drivers.Api; 11 | 12 | namespace Specs.Acceptance._CurrentSprint.MasterFiles.DisposalReasons.DeleteOne 13 | { 14 | public class DeleteExistingDisposalReason : ScenarioFor 15 | { 16 | private ApiResponse _result; 17 | private DisposalReason _existingItem; 18 | private List _updates; 19 | 20 | public void Given_an_existing_master_file() 21 | { 22 | _existingItem = new DisposalReasonBuilder().Persist(); 23 | } 24 | 25 | public async Task When_I_attempt_to_delete_it() 26 | { 27 | _result = await SUT.DeleteAsync(ApiRoutes.Master.DeleteFor(_existingItem.Id)); 28 | } 29 | 30 | public void Then_the_file_should_be_deleted() 31 | { 32 | _result.StatusCode.Should().Be(HttpStatusCode.NoContent); 33 | Db.Set().Should().HaveCount(0); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_CurrentSprint/MasterFiles/DisposalReasons/DeleteOne/DeleteNonExistentDisposalReason.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using ApiTemplate.Api.Application.Features.MasterFiles; 5 | using ApiTemplate.Api.Contracts; 6 | using ApiTemplate.Api.Domain.Model.MasterFiles; 7 | using FluentAssertions; 8 | using Specs.Library.Drivers.Api; 9 | 10 | namespace Specs.Acceptance._CurrentSprint.MasterFiles.DisposalReasons.DeleteOne 11 | { 12 | public class DeleteNonExistentDisposalReason : ScenarioFor 13 | { 14 | private ApiResponse _result; 15 | private List _updates; 16 | 17 | public void Given_I_am_trying_to_delete_a_file_that_does_not_exist() 18 | { 19 | } 20 | 21 | public async Task When_I_attempt_to_delete_it() 22 | { 23 | _result = await SUT.DeleteAsync(ApiRoutes.Master.DeleteFor(99)); 24 | } 25 | 26 | public void Then_I_should_be_warned_that_the_item_does_not_exist() 27 | { 28 | _result.StatusCode.Should().Be(HttpStatusCode.NotFound); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_CurrentSprint/MasterFiles/DisposalReasons/DisposalReasonStory.cs: -------------------------------------------------------------------------------- 1 | using Specs.Acceptance._CurrentSprint.MasterFiles._BaseSpecifications; 2 | 3 | namespace Specs.Acceptance._CurrentSprint.MasterFiles.DisposalReasons 4 | { 5 | public class DisposalReasonStory : MasterFilesStory 6 | { 7 | protected override string EntityName => "Disposal Reason"; 8 | } 9 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_CurrentSprint/MasterFiles/DisposalReasons/GetAll/GetAllDisposalReasonsWhenNone.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using ApiTemplate.Api.Application.Common.Paging; 4 | using ApiTemplate.Api.Application.Features.MasterFiles; 5 | using ApiTemplate.Api.Contracts; 6 | using ApiTemplate.Api.Domain.Model.MasterFiles; 7 | using FluentAssertions; 8 | using Specs.Library.Drivers.Api; 9 | 10 | namespace Specs.Acceptance._CurrentSprint.MasterFiles.DisposalReasons.GetAll 11 | { 12 | public class GetAllDisposalReasonsWhenNone : ScenarioFor 13 | { 14 | private ApiResponse> _result; 15 | 16 | public void Given_there_are_no_DisposalReasons() 17 | { 18 | } 19 | 20 | public async Task When_I_view_the_list() 21 | { 22 | _result = await SUT.GetAllPagedAsync(ApiRoutes.Master.GetAllFor()); 23 | } 24 | 25 | public void Then_I_should_see_an_empty_list() 26 | { 27 | _result.Model.Results.Count.Should().Be(0); 28 | _result.StatusCode.Should().Be(HttpStatusCode.OK); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_CurrentSprint/MasterFiles/DisposalReasons/GetAll/GetAllDisposalReasonsWithInvalidQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using ApiTemplate.Api.Application.Common.Paging; 3 | using ApiTemplate.Api.Application.Features.MasterFiles; 4 | using ApiTemplate.Api.Contracts; 5 | using ApiTemplate.Api.Domain.Model.MasterFiles; 6 | using FluentAssertions; 7 | using Specs.Library.Drivers.Api; 8 | using Specs.Library.Extensions; 9 | 10 | namespace Specs.Acceptance._CurrentSprint.MasterFiles.DisposalReasons.GetAll 11 | { 12 | public class GetAllDisposalReasonsWithInvalidQuery : ScenarioFor 13 | { 14 | private ApiResponse> _result; 15 | 16 | public async Task When_I_search_with_invalid_search_parameters() 17 | { 18 | // FilterField without FilterText is invalid 19 | _result = await SUT.GetAllPagedAsync( 20 | ApiRoutes.Master.GetAllFor(pageSize: 3, page: 2, filterField: "DisposalReasonDescription")); 21 | } 22 | 23 | public void Then_I_am_advised_of_the_error() 24 | { 25 | _result.ShouldHaveError("FilterText", "'FilterText' is required."); 26 | } 27 | 28 | public void AndThen_no_results_are_returned() 29 | { 30 | _result.Model.Should().BeNull(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_CurrentSprint/MasterFiles/DisposalReasons/GetOne/GetOneExistingDisposalReason.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using ApiTemplate.Api.Application.Features.MasterFiles; 4 | using ApiTemplate.Api.Contracts; 5 | using ApiTemplate.Api.Domain.Model.MasterFiles; 6 | using FluentAssertions; 7 | using Specs.Library.Builders.Entities.MasterFiles; 8 | using Specs.Library.Data; 9 | using Specs.Library.Drivers.Api; 10 | 11 | namespace Specs.Acceptance._CurrentSprint.MasterFiles.DisposalReasons.GetOne 12 | { 13 | public class GetOneExistingDisposalReason : ScenarioFor 14 | { 15 | private ApiResponse _result; 16 | private DisposalReason _existing; 17 | 18 | public void Given_I_have_created_a_master_file() 19 | { 20 | _existing = new DisposalReasonBuilder().Persist(); 21 | } 22 | 23 | public async Task When_I_attempt_to_view_it() 24 | { 25 | _result = await SUT.GetAsync(ApiRoutes.Master.GetFor(_existing.Id)); 26 | } 27 | 28 | public void Then_I_should_see_all_the_files() 29 | { 30 | _result.StatusCode.Should().Be(HttpStatusCode.OK); 31 | _result.Model.Id.Should().Be(_existing.Id); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_CurrentSprint/MasterFiles/DisposalReasons/GetOne/GetOneNonExistingDisposalReason.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using ApiTemplate.Api.Application.Features.MasterFiles; 4 | using ApiTemplate.Api.Contracts; 5 | using ApiTemplate.Api.Domain.Model.MasterFiles; 6 | using FluentAssertions; 7 | using Specs.Library.Drivers.Api; 8 | 9 | namespace Specs.Acceptance._CurrentSprint.MasterFiles.DisposalReasons.GetOne 10 | { 11 | public class GetOneNonExistingDisposalReason : ScenarioFor 12 | { 13 | private ApiResponse _result; 14 | 15 | public async Task When_I_attempt_to_view_a_master_file_that_does_not_exist() 16 | { 17 | _result = await SUT.GetAsync(ApiRoutes.Master.GetFor(99)); 18 | } 19 | 20 | public void Then_I_should_be_warned_that_it_does_not_exist() 21 | { 22 | _result.StatusCode.Should().Be(HttpStatusCode.NotFound); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_CurrentSprint/MasterFiles/DisposalReasons/UpdateMany/UpdateNonExistentDisposalReason.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using ApiTemplate.Api.Application.Features.MasterFiles; 5 | using ApiTemplate.Api.Contracts; 6 | using ApiTemplate.Api.Domain.Model.MasterFiles; 7 | using FluentAssertions; 8 | using Specs.Library.Builders; 9 | using Specs.Library.Drivers.Api; 10 | 11 | namespace Specs.Acceptance._CurrentSprint.MasterFiles.DisposalReasons.UpdateMany 12 | { 13 | public class UpdateNonExistentDisposalReason : ScenarioFor 14 | { 15 | private ApiResponse _result; 16 | private List _updates; 17 | 18 | public void Given_I_am_trying_to_edit_an_item_that_does_not_exist() 19 | { 20 | _updates = new List {Get.InstanceOf()}; 21 | } 22 | 23 | public async Task When_I_attempt_to_apply_changes_to_it() 24 | { 25 | _result = await SUT.PutAsync(ApiRoutes.Master.UpdateFor(), _updates); 26 | } 27 | 28 | public void Then_I_should_be_warned_that_the_item_does_not_exist() 29 | { 30 | _result.StatusCode.Should().Be(HttpStatusCode.NotFound); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_CurrentSprint/MasterFiles/_BaseSpecifications/MasterFilesStory.cs: -------------------------------------------------------------------------------- 1 | using Specify.Stories; 2 | 3 | namespace Specs.Acceptance._CurrentSprint.MasterFiles._BaseSpecifications 4 | { 5 | public abstract class MasterFilesStory : UserStory 6 | { 7 | protected abstract string EntityName { get; } 8 | 9 | protected MasterFilesStory() 10 | { 11 | AsA = "As a Farm Manager"; 12 | IWant = $"I want to update my {EntityName} Master Files"; 13 | SoThat = "So that I can add list data that is custom to my organisation"; 14 | TitlePrefix = $"Story: Master Files - "; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_CurrentSprint/ReadMe.md: -------------------------------------------------------------------------------- 1 | This holds the specs that are being worked on in each sprint. 2 | As the sprint progresses you can see the progress of functionality being implemented as these specs pass. 3 | At the end of the sprint, the specs will be moved into the Application folder in the appropriate place alongside similar related features. 4 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_TemporarySampleSpecs/ReadMe.md: -------------------------------------------------------------------------------- 1 | This folder holds a fullish suite of specs for the sample ToDo endpoints, which covers the main HTTP verbs. 2 | Hopefully, it provides a useful starting point for examples of the sorts of specs required for each of these endpoints. -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_TemporarySampleSpecs/ToDoItems/Create/CreateInvalidToDo.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using ApiTemplate.Api.Application.Features.ToDoItems; 3 | using ApiTemplate.Api.Contracts; 4 | using ApiTemplate.Api.Contracts.Responses; 5 | using ApiTemplate.Api.Domain.Model.ToDos; 6 | using FluentAssertions; 7 | using Specs.Library.Drivers.Api; 8 | using Specs.Library.Extensions; 9 | 10 | namespace Specs.Acceptance._TemporarySampleSpecs.ToDoItems.Create 11 | { 12 | public class CreateInvalidToDo : ScenarioFor 13 | { 14 | private ApiResponse _result; 15 | private CreateTodoItemCommand _command; 16 | 17 | public void Given_I_have_composed_an_invalid_new_ToDo() 18 | { 19 | _command = new CreateTodoItemCommand(); 20 | } 21 | 22 | public async Task When_I_attempt_to_create_it() 23 | { 24 | _result = await SUT.PostAsync(ApiRoutes.ToDo.Create, _command); 25 | } 26 | 27 | public void Then_the_new_ToDo_is_not_created() 28 | { 29 | Db.Set().Should().HaveCount(0); 30 | } 31 | 32 | public void And_then_I_should_be_told_the_reasons_why() 33 | { 34 | _result.ShouldContainErrors( 35 | "'Title' must not be empty.", 36 | "'Description' must not be empty."); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_TemporarySampleSpecs/ToDoItems/Delete/DeleteNonExistingToDo.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using ApiTemplate.Api.Contracts; 4 | using FluentAssertions; 5 | using Specs.Library.Drivers.Api; 6 | 7 | namespace Specs.Acceptance._TemporarySampleSpecs.ToDoItems.Delete 8 | { 9 | public class DeleteNonExistingToDo : ScenarioFor 10 | { 11 | private ApiResponse _result; 12 | 13 | public async Task When_I_attempt_to_delete_a_todo_that_does_not_exist() 14 | { 15 | _result = await SUT.DeleteAsync(ApiRoutes.ToDo.DeleteFor(99)); 16 | } 17 | 18 | public void Then_I_should_receive_a_not_found_warning() 19 | { 20 | _result.StatusCode.Should().Be(HttpStatusCode.NotFound); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_TemporarySampleSpecs/ToDoItems/Delete/ValidDelete.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using ApiTemplate.Api.Contracts; 3 | using ApiTemplate.Api.Domain.Model.ToDos; 4 | using FluentAssertions; 5 | using Specs.Library.Builders.Entities; 6 | using Specs.Library.Data; 7 | using Specs.Library.Drivers.Api; 8 | 9 | namespace Specs.Acceptance._TemporarySampleSpecs.ToDoItems.Delete 10 | { 11 | public class ValidDelete : ScenarioFor 12 | { 13 | private ApiResponse _result; 14 | private ToDoItem _existingItem; 15 | 16 | public void Given_I_have_an_existing_todo() 17 | { 18 | _existingItem = new ToDoItemBuilder().Persist(); 19 | } 20 | 21 | public async Task When_I_attempt_to_delete_it() 22 | { 23 | _result = await SUT.DeleteWithCheckAsync(ApiRoutes.ToDo.DeleteFor(_existingItem.Id)); 24 | } 25 | 26 | public void Then_the_todo_should_be_deleted() 27 | { 28 | Db.Set().Should().HaveCount(0); 29 | // _result.StatusCode.Should().Be(StatusCodes.Status204NoContent); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_TemporarySampleSpecs/ToDoItems/GetAll/GetAllToDos.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using ApiTemplate.Api.Application.Features.ToDoItems; 5 | using ApiTemplate.Api.Contracts; 6 | using FluentAssertions; 7 | using Specs.Library.Builders.Entities; 8 | using Specs.Library.Data; 9 | using Specs.Library.Drivers.Api; 10 | 11 | namespace Specs.Acceptance._TemporarySampleSpecs.ToDoItems.GetAll 12 | { 13 | public class GetAllToDos : ScenarioFor 14 | { 15 | private ApiResponse> _result; 16 | 17 | public void Given_I_have_created_a_list_of_things_to_do() 18 | { 19 | ToDoItemBuilder.CreateDefaultList().Persist(); 20 | } 21 | 22 | public async Task When_I_view_my_list() 23 | { 24 | _result = await SUT.GetAllAsync(ApiRoutes.ToDo.GetAll); 25 | } 26 | 27 | public void Then_I_should_see_all_the_things_I_have_to_do() 28 | { 29 | _result.Model.Count.Should().Be(3); 30 | _result.StatusCode.Should().Be(HttpStatusCode.OK); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_TemporarySampleSpecs/ToDoItems/GetAll/GetAllToDosWhenNone.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using ApiTemplate.Api.Application.Features.ToDoItems; 5 | using ApiTemplate.Api.Contracts; 6 | using FluentAssertions; 7 | using Specs.Library.Drivers.Api; 8 | 9 | namespace Specs.Acceptance._TemporarySampleSpecs.ToDoItems.GetAll 10 | { 11 | public class GetAllToDosWhenNone : ScenarioFor 12 | { 13 | private ApiResponse> _result; 14 | 15 | public void Given_I_do_not_have_any_things_to_do() 16 | { 17 | } 18 | 19 | public async Task When_I_view_my_to_do_list() 20 | { 21 | _result = await SUT.GetAllAsync(ApiRoutes.ToDo.GetAll); 22 | } 23 | 24 | public void Then_I_should_see_an_empty_list() 25 | { 26 | _result.Model.Count.Should().Be(0); 27 | _result.StatusCode.Should().Be(HttpStatusCode.OK); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_TemporarySampleSpecs/ToDoItems/GetOne/GetExistingToDo.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using ApiTemplate.Api.Application.Features.ToDoItems; 4 | using ApiTemplate.Api.Contracts; 5 | using ApiTemplate.Api.Domain.Model.ToDos; 6 | using FluentAssertions; 7 | using Specs.Library.Builders.Entities; 8 | using Specs.Library.Data; 9 | using Specs.Library.Drivers.Api; 10 | 11 | namespace Specs.Acceptance._TemporarySampleSpecs.ToDoItems.GetOne 12 | { 13 | public class GetExistingToDo : ScenarioFor 14 | { 15 | private ApiResponse _result; 16 | private ToDoItem _existingItem; 17 | 18 | public void Given_I_have_created_a_to_do() 19 | { 20 | _existingItem = new ToDoItemBuilder().Persist(); 21 | } 22 | 23 | public async Task When_I_attempt_to_view_it() 24 | { 25 | _result = await SUT.GetAsync(ApiRoutes.ToDo.GetFor(_existingItem.Id)); 26 | } 27 | 28 | public void Then_I_should_see_all_the_things_I_have_to_do() 29 | { 30 | _result.StatusCode.Should().Be(HttpStatusCode.OK); 31 | _result.Model.Id.Should().Be(_existingItem.Id); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_TemporarySampleSpecs/ToDoItems/GetOne/GetNonExistingToDo.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using ApiTemplate.Api.Application.Features.ToDoItems; 4 | using ApiTemplate.Api.Contracts; 5 | using FluentAssertions; 6 | using Specs.Library.Drivers.Api; 7 | 8 | namespace Specs.Acceptance._TemporarySampleSpecs.ToDoItems.GetOne 9 | { 10 | public class GetNonExistingToDo : ScenarioFor 11 | { 12 | private ApiResponse _result; 13 | 14 | public async Task When_I_attempt_to_view_a_todo_that_does_not_exist() 15 | { 16 | _result = await SUT.GetAsync(ApiRoutes.ToDo.GetFor(99)); 17 | } 18 | 19 | public void Then_I_should_receive_a_not_found_warning() 20 | { 21 | //_result.Model.Should().BeNull(); 22 | _result.StatusCode.Should().Be(HttpStatusCode.NotFound); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_TemporarySampleSpecs/ToDoItems/ToDoStory.cs: -------------------------------------------------------------------------------- 1 | using Specify.Stories; 2 | 3 | namespace Specs.Acceptance._TemporarySampleSpecs.ToDoItems 4 | { 5 | public class ToDoStory : UserStory 6 | { 7 | public ToDoStory() 8 | { 9 | AsA = "As a User"; 10 | IWant = "I want to view my list of things I want to do"; 11 | SoThat = "So that I can decide what to do today."; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Acceptance/_TemporarySampleSpecs/ToDoItems/Update/UpdateNonExistentToDo.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using ApiTemplate.Api.Application.Features.ToDoItems; 4 | using ApiTemplate.Api.Contracts; 5 | using FluentAssertions; 6 | using Specs.Library.Builders; 7 | using Specs.Library.Drivers.Api; 8 | 9 | namespace Specs.Acceptance._TemporarySampleSpecs.ToDoItems.Update 10 | { 11 | public class UpdateNonExistentToDo : ScenarioFor 12 | { 13 | private ApiResponse _result; 14 | private UpdateTodoItemCommand _updates; 15 | 16 | public void Given_I_am_trying_to_edit_a_ToDo_that_does_not_exist() 17 | { 18 | _updates = Get.InstanceOf(); 19 | } 20 | 21 | public async Task When_I_attempt_to_apply_changes_to_it() 22 | { 23 | _result = await SUT.PutAsync(ApiRoutes.ToDo.Update, _updates); 24 | } 25 | 26 | public void Then_I_should_be_warned_that_the_ToDo_does_not_exist() 27 | { 28 | _result.StatusCode.Should().Be(HttpStatusCode.NotFound); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Integration/Api/Configuration/AutofacConventions.cs: -------------------------------------------------------------------------------- 1 | //using Autofac; 2 | //using Autofac.Core; 3 | //using TestStack.ConventionTests; 4 | //using TestStack.ConventionTests.Autofac; 5 | 6 | //namespace Specs.Integration.ApiTemplate.Api.Configuration 7 | //{ 8 | // public class AutofacConventions : ScenarioFor 9 | // { 10 | // private AutofacRegistrations _autofacRegistrations; 11 | 12 | // public override string Title => "Autofac Container"; 13 | 14 | // public void Given_all_the_services_registered_in_Autofac_container() 15 | // { 16 | // _autofacRegistrations = new AutofacRegistrations(SUT.ComponentRegistry); 17 | // } 18 | 19 | // public void When_I_attempt_to_resolve_each_service() 20 | // { 21 | 22 | // } 23 | 24 | // public void Then_should_be_able_to_resolve_all_registered_services() 25 | // { 26 | // IContainer container = SUT as IContainer; 27 | // Convention.Is(new CanResolveAllRegisteredServices(container), _autofacRegistrations); 28 | // } 29 | 30 | // public void AndThen_services_should_only_have_dependencies_with_lesser_lifetime() 31 | // { 32 | // Convention.Is(new ServicesShouldOnlyHaveDependenciesWithLesserLifetime(), _autofacRegistrations); 33 | // } 34 | // } 35 | //} 36 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Integration/Application/Features/MasterFiles/Validators/DisplayReasonValidatorSpecs.cs: -------------------------------------------------------------------------------- 1 | namespace Specs.Integration.ApiTemplate.Application.Features.MasterFiles.Validators 2 | { 3 | public class DisplayReasonValidatorSpecs 4 | { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Integration/Infrastructure/Persistence/TransientFailureCausingCommandInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Common; 2 | using Microsoft.EntityFrameworkCore.Diagnostics; 3 | using Specs.Library.Data; 4 | 5 | namespace Specs.Integration.ApiTemplate.Infrastructure.Persistence 6 | { 7 | public class TransientFailureCausingCommandInterceptor : DbCommandInterceptor 8 | { 9 | public int NumberOfTimesToThrow { get; } 10 | public static int RetryRunningTotal = 0; 11 | 12 | public TransientFailureCausingCommandInterceptor(int numberOfTimesToThrow = 3) 13 | { 14 | NumberOfTimesToThrow = numberOfTimesToThrow; 15 | } 16 | 17 | public override InterceptionResult ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult result) 18 | { 19 | const int ErrorNumber = 49920; 20 | RetryRunningTotal++; 21 | if (RetryRunningTotal % NumberOfTimesToThrow != 0) 22 | { 23 | throw SqlExceptionFactory.CreateSqlException(ErrorNumber); 24 | } 25 | return base.ReaderExecuting(command, eventData, result); 26 | } 27 | 28 | public static void Reset() 29 | { 30 | RetryRunningTotal = 0; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Integration/ScenarioFor.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Specify.Stories; 3 | using Specs.Library.Helpers; 4 | 5 | namespace Specs.Integration.ApiTemplate 6 | { 7 | /// 8 | /// The base class for scenarios without a story (normally unit test scenarios). 9 | /// 10 | /// The type of the t sut. 11 | [TestFixture] 12 | public abstract class ScenarioFor : Specify.ScenarioFor 13 | where TSut : class 14 | { 15 | [ExecuteScenario] 16 | public override void Specify() 17 | { 18 | base.Specify(); 19 | } 20 | } 21 | 22 | /// 23 | /// The base class for scenarios with a story (BDD-style acceptance tests). 24 | /// 25 | /// The type of the SUT. 26 | /// The type of the t story. 27 | [TestFixture] 28 | public abstract class ScenarioFor : Specify.ScenarioFor 29 | where TSut : class 30 | where TStory : Story, new() 31 | { 32 | [ExecuteScenario] 33 | public override void Specify() 34 | { 35 | base.Specify(); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Integration/TestsFor.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace Specs.Integration.ApiTemplate 4 | { 5 | [TestFixture] 6 | public abstract class TestsFor : Specify.TestsFor where TSut : class 7 | { 8 | [SetUp] 9 | public virtual void SetUp() 10 | { 11 | BaseSetup(); 12 | } 13 | 14 | [TearDown] 15 | public virtual void TearDown() 16 | { 17 | BaseTearDown(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Builders/Entities/MasterFiles/MasterFileBuilder.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Domain.Common; 2 | using TestStack.Dossier; 3 | 4 | namespace Specs.Library.Builders.Entities.MasterFiles 5 | { 6 | public abstract class MasterFileBuilder : TestDataBuilder 7 | where TObject : MasterFile 8 | where TBuilder : TestDataBuilder, new() 9 | { 10 | protected MasterFileBuilder() 11 | { 12 | Set(x => x.ActiveFlag, true); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Builders/Entities/MasterFiles/WeatherConditionBuilder.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Domain.Model.MasterFiles; 2 | using Specs.Library.Data; 3 | using TestStack.Dossier; 4 | using TestStack.Dossier.Lists; 5 | 6 | namespace Specs.Library.Builders.Entities.MasterFiles 7 | { 8 | public class WeatherConditionBuilder : MasterFileBuilder 9 | { 10 | public WeatherConditionBuilder() 11 | { 12 | Set(x => x.Condition, Any.Company.Name); 13 | } 14 | 15 | public static WeatherConditionBuilder WithPersistedType() 16 | { 17 | var weatherType = Builder.CreateNew().Persist(); 18 | return new WeatherConditionBuilder() 19 | .Set(x => x.WeatherType, weatherType); 20 | } 21 | 22 | public static ListBuilder CreateListWithPersistedType(int size) 23 | { 24 | var weatherType = Builder.CreateNew().Persist(); 25 | return WeatherConditionBuilder.CreateListOfSize(size) 26 | .All() 27 | .Set(x => x.WeatherType, weatherType) 28 | .ListBuilder; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Builders/Entities/ToDoItemBuilder.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Domain.Model.ToDos; 2 | using TestStack.Dossier; 3 | using TestStack.Dossier.Lists; 4 | 5 | namespace Specs.Library.Builders.Entities 6 | { 7 | public class ToDoItemBuilder : TestDataBuilder 8 | { 9 | public ToDoItemBuilder() 10 | { 11 | Set(x => x.Email, Email.Create(Any.Person.EmailAddress()).Value); 12 | } 13 | 14 | public static ListBuilder CreateDefaultList(int size = 3) 15 | { 16 | return ToDoItemBuilder.CreateListOfSize(size) 17 | .All() 18 | .Set(x => x.Email, () => Email.Create(Builders.Get.Any.Person.EmailAddress()).Value) 19 | .ListBuilder; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Builders/Get.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using Specs.Library.Builders.ObjectMothers; 3 | using TestStack.Dossier; 4 | 5 | namespace Specs.Library.Builders 6 | { 7 | public static class Get 8 | { 9 | public static Builder BuilderFor() where T : class 10 | { 11 | return Builder.CreateNew(); 12 | } 13 | 14 | public static T InstanceOf() where T : class 15 | { 16 | return Builder.CreateNew().Build(); 17 | } 18 | 19 | public static Stubs StubFor { get; } = new Stubs(); 20 | 21 | public static AnonymousValueFixture Any { get; } = new AnonymousValueFixture(); 22 | 23 | public static T AutoFixtureValueFor() 24 | { 25 | return new AnonymousValueFixture().Fixture.Create(); 26 | } 27 | 28 | public static SequentialMother SequenceOf { get; } = new SequentialMother(); 29 | } 30 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Builders/ValueSuppliers/CodeValueSupplier.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using TestStack.Dossier; 3 | using TestStack.Dossier.DataSources.Dictionaries; 4 | using TestStack.Dossier.DataSources.Picking; 5 | 6 | namespace Specs.Library.Builders.ValueSuppliers 7 | { 8 | public class CodeValueSupplier : IAnonymousValueSupplier 9 | { 10 | private RepeatingSequenceSource _dataSource ; 11 | 12 | public CodeValueSupplier() 13 | { 14 | var wordList = new Words(FromDictionary.FinanceCurrencyCode).Data; 15 | _dataSource = new RepeatingSequenceSource(wordList); 16 | } 17 | 18 | /// 19 | public bool CanSupplyValue(Type type, string propertyName) 20 | { 21 | return type == typeof(string) 22 | && (propertyName.ToLower().EndsWith("code") || propertyName.ToLower().EndsWith("name")); 23 | } 24 | 25 | /// 26 | public object GenerateAnonymousValue(AnonymousValueFixture any, Type type, string propertyName) 27 | { 28 | var code = _dataSource.Next(); 29 | return (object)code; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Data/DatabaseApplicationAction.cs: -------------------------------------------------------------------------------- 1 | using Specify.Configuration; 2 | 3 | namespace Specs.Library.Data 4 | { 5 | public class DatabaseApplicationAction : IPerApplicationAction 6 | { 7 | private readonly IDbFactory _dbFactory; 8 | private readonly IDb _db; 9 | 10 | public DatabaseApplicationAction(IDbFactory dbFactory, IDb db) 11 | { 12 | _dbFactory = dbFactory; 13 | _db = db; 14 | } 15 | 16 | public void Before() 17 | { 18 | if (TestSettings.ShouldCreateDatabase) 19 | { 20 | _dbFactory.CreateDatabase(); 21 | } 22 | 23 | EntityExtensions.Db = _db; 24 | } 25 | 26 | public void After() 27 | { 28 | 29 | } 30 | 31 | public int Order { get; set; } = 3; 32 | } 33 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Data/IDb.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ApiTemplate.Api.Domain.Common; 3 | using ApiTemplate.Api.Infrastructure.Persistence; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace Specs.Library.Data 7 | { 8 | public interface IDb 9 | { 10 | AppDbContext CreateContext(bool beginTransaction = false); 11 | void ExecuteDbContext(Action action); 12 | T ExecuteDbContext(Func action); 13 | 14 | long Insert(Entity entity) where T : Entity; 15 | long Insert(params Entity[] entities) where T : Entity; 16 | void IdentityInsert(T entity, string tableName) where T : Entity; 17 | 18 | void Update(Entity entity) where T : Entity; 19 | void Update(params Entity[] entities) where T : Entity; 20 | 21 | DbSet Set () where T : Entity; 22 | 23 | T Find(int id) where T : Entity; 24 | } 25 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Data/IDbFactory.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Infrastructure.Persistence; 2 | 3 | namespace Specs.Library.Data 4 | { 5 | public interface IDbFactory 6 | { 7 | AppDbContext CreateContext(bool beginTransaction = false); 8 | void CreateDatabase(); 9 | void DeleteDatabase(); 10 | void ResetData(); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Data/ResetDatabaseAction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Specify; 3 | using Specify.Configuration; 4 | 5 | namespace Specs.Library.Data 6 | { 7 | public class ResetDatabaseAction : IPerScenarioAction 8 | { 9 | public void Before(IScenario scenario) where TSut : class 10 | { 11 | scenario.Container.Get().ResetData(); 12 | } 13 | 14 | public void After() 15 | { 16 | } 17 | 18 | public bool ShouldExecute(Type type) 19 | { 20 | return true; 21 | } 22 | 23 | public int Order { get; set; } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Data/ResetSystemTimeAction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ApiTemplate.Api.Domain.Common; 3 | using Specify; 4 | using Specify.Configuration; 5 | 6 | namespace Specs.Library.Data 7 | { 8 | public class ResetSystemTimeAction : IPerScenarioAction 9 | { 10 | public void Before(IScenario scenario) where TSut : class 11 | { 12 | } 13 | 14 | public void After() 15 | { 16 | SystemTime.Reset(); 17 | } 18 | 19 | public bool ShouldExecute(Type type) 20 | { 21 | return true; 22 | } 23 | 24 | public int Order { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Drivers/Api/ApiDriverException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Specs.Library.Drivers.Api 4 | { 5 | public class ApiDriverException : Exception 6 | { 7 | public ApiDriverException(string message) 8 | : base(message) { } 9 | 10 | public ApiDriverException(string message, Exception inner) 11 | : base(message, inner) { } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Drivers/Api/ITestHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | 4 | namespace Specs.Library.Drivers.Api 5 | { 6 | public interface ITestHost 7 | { 8 | Uri BaseAddress { get; } 9 | string AppName { get; } 10 | HttpClient Client { get; } 11 | void Start(); 12 | void Stop(); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Drivers/Api/JsonExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Text; 3 | using Newtonsoft.Json; 4 | 5 | namespace Specs.Library.Drivers.Api 6 | { 7 | public static class JsonExtensions 8 | { 9 | public static string AsJson(this object entity) 10 | { 11 | return JsonConvert.SerializeObject(entity); 12 | } 13 | 14 | public static StringContent AsJsonContent(this string entity) 15 | { 16 | return new StringContent(entity, Encoding.UTF8, "application/json"); 17 | } 18 | 19 | public static StringContent AsJsonContent(this object entity) 20 | { 21 | var payload = entity.AsJson(); 22 | return new StringContent(payload, Encoding.UTF8, "application/json"); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Drivers/Api/MemoryHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using Microsoft.AspNetCore.TestHost; 4 | 5 | namespace Specs.Library.Drivers.Api 6 | { 7 | public class MemoryHost : ITestHost 8 | { 9 | private readonly TestServer _server; 10 | private HttpClient _client; 11 | 12 | public MemoryHost(TestServer server) 13 | { 14 | _server = server; 15 | } 16 | 17 | public Uri BaseAddress => new Uri("http://localhost/"); 18 | 19 | public string AppName => "Memory Host - ApiTemplate.Api"; 20 | 21 | // Client is singleton: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ 22 | public HttpClient Client => _client ??= CreateHttpClient(); 23 | 24 | public void Start() 25 | { 26 | // TestServer already created 27 | } 28 | 29 | public void Stop() 30 | { 31 | try 32 | { 33 | _server.Dispose(); 34 | _client.Dispose(); 35 | } 36 | catch (Exception e) 37 | { 38 | Console.WriteLine("Could not stop server: {0}", e); 39 | } 40 | } 41 | 42 | private HttpClient CreateHttpClient() 43 | { 44 | return _server.CreateClient(); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Extensions/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Reflection; 3 | 4 | namespace Specs.Library.Extensions 5 | { 6 | public static class ObjectExtensions 7 | { 8 | public static T MapTo(this object source, T target) 9 | { 10 | foreach (PropertyInfo sourceProp in source.GetType().GetProperties()) 11 | { 12 | PropertyInfo targetProp = target.GetType().GetProperties().FirstOrDefault(p => p.Name == sourceProp.Name); 13 | if (targetProp != null && targetProp.GetType().Name == sourceProp.GetType().Name) 14 | { 15 | targetProp.SetValue(target, sourceProp.GetValue(source)); 16 | } 17 | } 18 | return target; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Extensions/ResultSpecExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using FluentResults; 3 | 4 | namespace Specs.Library.Extensions 5 | { 6 | public static class ResultSpecExtensions 7 | { 8 | public static void ShouldHaveError(this ResultBase result, string errorMessage) 9 | { 10 | result.IsFailed.Should().BeTrue(); 11 | result.Errors[0].Message.Should().Be(errorMessage); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using System.Xml.Serialization; 4 | 5 | namespace Specs.Library.Extensions 6 | { 7 | public static class StringExtensions 8 | { 9 | public static string Repeat(this string s, int n) 10 | { 11 | return new StringBuilder(s.Length * n) 12 | .AppendJoin(s, new string[n + 1]) 13 | .ToString(); 14 | } 15 | 16 | public static T FromXmlStringToType(this string xml) 17 | { 18 | T instance; 19 | var xmlSerializer = new XmlSerializer(typeof(T)); 20 | using (var sr = new StringReader(xml)) 21 | { 22 | instance = (T)xmlSerializer.Deserialize(sr); 23 | } 24 | return instance; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Helpers/AsyncHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Specs.Library.Helpers 6 | { 7 | // helper class from Microsoft source code that is normally internal. i.e. Not available in a nuget package. 8 | // https://cpratt.co/async-tips-tricks/ 9 | public static class AsyncHelper 10 | { 11 | private static readonly TaskFactory _myTaskFactory = new 12 | TaskFactory(CancellationToken.None, 13 | TaskCreationOptions.None, 14 | TaskContinuationOptions.None, 15 | TaskScheduler.Default); 16 | 17 | public static TResult RunSync(Func> func) 18 | { 19 | return _myTaskFactory 20 | .StartNew>(func) 21 | .Unwrap() 22 | .GetAwaiter() 23 | .GetResult(); 24 | } 25 | 26 | public static void RunSync(Func func) 27 | { 28 | _myTaskFactory 29 | .StartNew(func) 30 | .Unwrap() 31 | .GetAwaiter() 32 | .GetResult(); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Helpers/ExecuteScenarioAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using NUnit.Framework.Interfaces; 4 | using NUnit.Framework.Internal; 5 | using NUnit.Framework.Internal.Builders; 6 | 7 | namespace Specs.Library.Helpers 8 | { 9 | /// 10 | /// Sets the NUnit TestName to the name of the scenario class. 11 | /// By default, NUnit would set every test with the name of the method ('Specify') 12 | /// 13 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] 14 | public class ExecuteScenarioAttribute : NUnitAttribute, ISimpleTestBuilder 15 | { 16 | public TestMethod BuildFrom(IMethodInfo method, Test suite) 17 | { 18 | var builder = new NUnitTestCaseBuilder(); 19 | var testMethod = builder.BuildTestMethod(method, suite, null); 20 | testMethod.Name = method.TypeInfo.Name; 21 | return testMethod; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Helpers/TestableSystemTime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ApiTemplate.Api.Domain.Common; 3 | 4 | namespace Specs.Library.Helpers 5 | { 6 | public class TestableSystemTime : IDisposable 7 | { 8 | public TestableSystemTime(DateTime dateTime) 9 | { 10 | SystemTime.UtcNow = () => dateTime; 11 | } 12 | 13 | public TestableSystemTime(Func dateTimeFactory) 14 | { 15 | SystemTime.UtcNow = dateTimeFactory; 16 | } 17 | 18 | public void Dispose() 19 | { 20 | SystemTime.Reset(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/Identity/TestCurrentUserService.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Application.Common.Interfaces; 2 | 3 | namespace Specs.Library.Identity 4 | { 5 | public class TestCurrentUserService : ICurrentUserService 6 | { 7 | public int UserId => 1; 8 | } 9 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Library/README.md: -------------------------------------------------------------------------------- 1 | # Common Layer 2 | 3 | This will contain helper code that can be used across the other Specs projects. -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Unit/Application/Features/ToDoItems/Create/CreateToDoItemCommandValidatorSpecs.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Application.Features.ToDoItems; 2 | using FluentValidation.TestHelper; 3 | using Specs.Library.Extensions; 4 | 5 | namespace Specs.Unit.ApiTemplate.Application.Features.ToDoItems.Create 6 | { 7 | public class CreateToDoItemCommandValidatorSpecs: ScenarioFor 8 | { 9 | public void Then_should_have_Title_with_no_more_than_200_characters() 10 | { 11 | SUT.ShouldHaveValidationErrorFor(x => x.Title, "a".Repeat(201)) 12 | .WithErrorMessage("The length of 'Title' must be 200 characters or fewer. You entered 201 characters."); 13 | SUT.ShouldNotHaveValidationErrorFor(x => x.Title, "a".Repeat(100)); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Unit/Domain/Common/FluentResult/GetReasonOrDefaultSpecs.cs: -------------------------------------------------------------------------------- 1 | using ApiTemplate.Api.Domain.Common.FluentResult; 2 | using FluentAssertions; 3 | using FluentResults; 4 | 5 | namespace Specs.Unit.ApiTemplate.Domain.Common.FluentResult 6 | { 7 | public class GetReasonOrDefault : ScenarioFor 8 | { 9 | public void Given_a_Result_with_a_RecordCreatedReason() 10 | { 11 | SUT = Result.Ok().WithReason(new RecordsCreatedSuccess(22)); 12 | } 13 | 14 | public void When_GetReason_is_called() 15 | { 16 | 17 | } 18 | 19 | public void Then_should_return_reason_if_asked_for_RecordCreatedReason() 20 | { 21 | var reason = SUT.GetReasonOrDefault(); 22 | reason.Should().NotBeNull(); 23 | reason.Should().BeOfType(); 24 | } 25 | 26 | public void AndThen_should_return_null_if_asked_for_different_reason() 27 | { 28 | var reason = SUT.GetReasonOrDefault(); 29 | reason.Should().BeNull(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Unit/Domain/Common/FluentResult/GetReasonSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ApiTemplate.Api.Domain.Common.FluentResult; 3 | using FluentAssertions; 4 | using FluentResults; 5 | 6 | namespace Specs.Unit.ApiTemplate.Domain.Common.FluentResult 7 | { 8 | public class GetReason : ScenarioFor 9 | { 10 | public void Given_a_Result_with_a_Reason() 11 | { 12 | SUT = Result.Ok().WithReason(new RecordsNotFoundAppError("Id", 22)); 13 | } 14 | 15 | public void When_GetReason_is_called_for_that_type_of_Reason() 16 | { 17 | 18 | } 19 | 20 | public void Then_should_return_requested_Reason() 21 | { 22 | var reason = SUT.GetReason(); 23 | reason.Should().NotBeNull(); 24 | reason.Should().BeOfType(); 25 | } 26 | 27 | public void AndThen_should_throw_if_requested_Reason_missing() 28 | { 29 | Action action = () => SUT.GetReason(); 30 | action.Should().Throw(); 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Unit/ScenarioFor.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Specify.Stories; 3 | using Specs.Library.Helpers; 4 | 5 | namespace Specs.Unit.ApiTemplate 6 | { 7 | /// 8 | /// The base class for scenarios without a story (normally unit test scenarios). 9 | /// 10 | /// The type of the t sut. 11 | [TestFixture] 12 | public abstract class ScenarioFor : Specify.ScenarioFor 13 | where TSut : class 14 | { 15 | [ExecuteScenario] 16 | public override void Specify() 17 | { 18 | base.Specify(); 19 | } 20 | } 21 | 22 | /// 23 | /// The base class for scenarios with a story (BDD-style acceptance tests). 24 | /// 25 | /// The type of the SUT. 26 | /// The type of the t story. 27 | [TestFixture] 28 | public abstract class ScenarioFor : Specify.ScenarioFor 29 | where TSut : class 30 | where TStory : Story, new() 31 | { 32 | [ExecuteScenario] 33 | public override void Specify() 34 | { 35 | base.Specify(); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Unit/TestsFor.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace Specs.Unit.ApiTemplate 4 | { 5 | [TestFixture] 6 | public abstract class TestsFor : Specify.TestsFor where TSut : class 7 | { 8 | [SetUp] 9 | public virtual void SetUp() 10 | { 11 | BaseSetup(); 12 | } 13 | 14 | [TearDown] 15 | public virtual void TearDown() 16 | { 17 | BaseTearDown(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Samples/AspNetCoreApi/specs/Specs.Unit/UnitBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using DryIoc; 2 | using Specify.Configuration; 3 | using TestStack.BDDfy.Configuration; 4 | 5 | namespace Specs.Unit.ApiTemplate 6 | { 7 | /// 8 | /// The startup class to configure Specify with the default TinyIoc container. 9 | /// Make any changes to the default configuration settings in this file. 10 | /// 11 | public class UnitBootstrapper : DefaultBootstrapper 12 | { 13 | public UnitBootstrapper() 14 | { 15 | HtmlReport.ReportHeader = "API Template"; 16 | HtmlReport.ReportDescription = "Unit Specs"; 17 | HtmlReport.OutputFileName = "ApiTemplate-UnitSpecs.html"; 18 | Configurator.BatchProcessors.HtmlReport.Disable(); 19 | } 20 | 21 | /// 22 | /// Register any additional items into the DryIoc container or leave it as it is. 23 | /// 24 | /// The container. 25 | public override void ConfigureContainer(Container container) 26 | { 27 | // container.Register(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/Atm/Atm.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Samples.Domain.Atm 2 | { 3 | public class Atm 4 | { 5 | public int ExistingCash { get; private set; } 6 | 7 | public Atm(int existingCash) 8 | { 9 | ExistingCash = existingCash; 10 | } 11 | 12 | public void RequestMoney(Card card, int request) 13 | { 14 | if (!card.Enabled) 15 | { 16 | CardIsRetained = true; 17 | Message = DisplayMessage.CardIsRetained; 18 | return; 19 | } 20 | 21 | if(card.AccountBalance < request) 22 | { 23 | Message = DisplayMessage.InsufficientFunds; 24 | return; 25 | } 26 | 27 | DispenseValue = request; 28 | card.AccountBalance -= request; 29 | } 30 | 31 | public int DispenseValue { get; set; } 32 | 33 | public bool CardIsRetained { get; private set; } 34 | 35 | public DisplayMessage Message { get; private set; } 36 | } 37 | } -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/Atm/Card.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Samples.Domain.Atm 2 | { 3 | public class Card 4 | { 5 | public int AccountBalance { get; set; } 6 | private readonly bool _enabled; 7 | 8 | public Card(bool enabled, int accountBalance) 9 | { 10 | AccountBalance = accountBalance; 11 | _enabled = enabled; 12 | } 13 | 14 | public bool Enabled 15 | { 16 | get { return _enabled; } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/Atm/DisplayMessage.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Samples.Domain.Atm 2 | { 3 | public enum DisplayMessage 4 | { 5 | None = 0, 6 | CardIsRetained, 7 | InsufficientFunds 8 | } 9 | } -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/Katas/PrimeNumbers/PrimeCalculator.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace Specify.Samples.Domain.Katas.PrimeNumbers 4 | { 5 | public class PrimeCalculator 6 | { 7 | public bool IsPrime(int candidate) 8 | { 9 | return candidate != 1 && 10 | !Enumerable.Range(2, candidate) 11 | .Any(i => candidate != i && candidate % i == 0); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/OrderProcessing/Auditer.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace Specify.Samples.Domain.OrderProcessing 4 | { 5 | public sealed class Auditer 6 | { 7 | private readonly ILogger _logger; 8 | 9 | public Auditer(ILogger logger) 10 | { 11 | _logger = logger; 12 | } 13 | 14 | public void Audit(Order order) 15 | { 16 | _logger.Information(order.PartNumber); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/OrderProcessing/IInventory.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Samples.Domain.OrderProcessing 2 | { 3 | public interface IInventory 4 | { 5 | bool IsQuantityAvailable(string partNumber, int quantity); 6 | } 7 | } -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/OrderProcessing/Order.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Samples.Domain.OrderProcessing 2 | { 3 | public class Order 4 | { 5 | public string PartNumber { get; set; } 6 | 7 | public int Quantity { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/OrderProcessing/OrderProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Specify.Samples.Domain.OrderProcessing 4 | { 5 | public class OrderProcessor 6 | { 7 | private readonly IInventory _inventory; 8 | private readonly Publisher _publisher; 9 | private readonly Auditer _auditer; 10 | 11 | public OrderProcessor(IInventory inventory, Publisher publisher, Auditer auditer) 12 | { 13 | _inventory = inventory; 14 | _publisher = publisher; 15 | _auditer = auditer; 16 | } 17 | 18 | public virtual OrderResult Process(Order order) 19 | { 20 | _auditer.Audit(order); 21 | var result = new OrderResult(); 22 | 23 | if (order.Quantity < 0) 24 | { 25 | return result; 26 | } 27 | 28 | var available = _inventory.IsQuantityAvailable(order.PartNumber, order.Quantity); 29 | 30 | if (available) 31 | { 32 | result.WasAccepted = true; 33 | 34 | var orderNumber = Guid.NewGuid().ToString(); 35 | 36 | _publisher.Publish(new OrderSubmitted { OrderNumber = orderNumber }); 37 | 38 | result.OrderNumber = orderNumber; 39 | } 40 | 41 | return result; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/OrderProcessing/OrderResult.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Samples.Domain.OrderProcessing 2 | { 3 | public class OrderResult 4 | { 5 | public bool WasAccepted { get; set; } 6 | 7 | public string OrderNumber { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/OrderProcessing/OrderSubmitted.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Samples.Domain.OrderProcessing 2 | { 3 | public class OrderSubmitted 4 | { 5 | public string OrderNumber { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/OrderProcessing/Publisher.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Samples.Domain.OrderProcessing 2 | { 3 | public class Publisher 4 | { 5 | public virtual void Publish(TEvent @event) 6 | { 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/TrainFares/BuyerCategory.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Samples.Domain.TrainFares 2 | { 3 | public enum BuyerCategory 4 | { 5 | Student, 6 | Senior, 7 | Standard 8 | } 9 | } -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/TrainFares/DayPass.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Samples.Domain.TrainFares 2 | { 3 | public class DayPass : Fare 4 | { 5 | public override string ToString() 6 | { 7 | return "Day Pass"; 8 | } 9 | 10 | protected override Money Cost => new Money(10); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/TrainFares/Fare.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Samples.Domain.TrainFares 2 | { 3 | public abstract class Fare 4 | { 5 | protected abstract Money Cost { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/TrainFares/Money.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace Specify.Samples.Domain.TrainFares 4 | { 5 | public class Money 6 | { 7 | public Money(decimal amount) 8 | { 9 | Amount = amount; 10 | } 11 | 12 | public decimal Amount { get; set; } 13 | 14 | protected bool Equals(Money other) 15 | { 16 | return Amount == other.Amount; 17 | } 18 | 19 | public override bool Equals(object obj) 20 | { 21 | if (ReferenceEquals(null, obj)) return false; 22 | if (ReferenceEquals(this, obj)) return true; 23 | if (obj.GetType() != GetType()) return false; 24 | return Equals((Money)obj); 25 | } 26 | 27 | public override int GetHashCode() 28 | { 29 | return Amount.GetHashCode(); 30 | } 31 | 32 | public static bool operator ==(Money left, Money right) 33 | { 34 | return Equals(left, right); 35 | } 36 | 37 | public static bool operator !=(Money left, Money right) 38 | { 39 | return !Equals(left, right); 40 | } 41 | 42 | public override string ToString() 43 | { 44 | return Amount.ToString("C", new CultureInfo("EN-US")); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/TrainFares/MonthlyPass.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Samples.Domain.TrainFares 2 | { 3 | public class MonthlyPass : Fare 4 | { 5 | public override string ToString() 6 | { 7 | return "Monthly Pass"; 8 | } 9 | 10 | protected override Money Cost => new Money(150); 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/TrainFares/SingleTicket.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Samples.Domain.TrainFares 2 | { 3 | public class SingleTicket : Fare 4 | { 5 | public override string ToString() 6 | { 7 | return "Single Ticket"; 8 | } 9 | 10 | protected override Money Cost => new Money(3); 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Domain/TrainFares/WeeklyPass.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Samples.Domain.TrainFares 2 | { 3 | public class WeeklyPass : Fare 4 | { 5 | public override string ToString() 6 | { 7 | return "Weekly Pass"; 8 | } 9 | 10 | protected override Money Cost => new Money(50); 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/ScenarioFor.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Specify.Stories; 3 | 4 | namespace Specify.Samples 5 | { 6 | public abstract class ScenarioFor : Specify.ScenarioFor 7 | where TSut : class 8 | where TStory : Story, new() 9 | { 10 | [Test] 11 | public override void Specify() 12 | { 13 | base.Specify(); 14 | } 15 | } 16 | 17 | public abstract class ScenarioFor : Specify.ScenarioFor 18 | where TSut : class 19 | { 20 | [Test] 21 | public override void Specify() 22 | { 23 | base.Specify(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Specify.Samples.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Specs/ATM/AccountHolderWithdrawsCashStory.cs: -------------------------------------------------------------------------------- 1 | using Specify.Stories; 2 | 3 | namespace Specify.Samples.Specs.ATM 4 | { 5 | public class AccountHolderWithdrawsCashStory : UserStory 6 | { 7 | public AccountHolderWithdrawsCashStory() 8 | { 9 | AsA = "As an Account Holder"; 10 | IWant = "I want to withdraw cash from an ATM"; 11 | SoThat = "So that I can get money when the bank is closed"; 12 | //ImageUri = "https://upload.wikimedia.org/wikipedia/commons/d/d3/49024-SOS-ATM.JPG"; 13 | //StoryUri = "http://google.com"; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Specs/ATM/AtmHtmlReportConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Samples.Specs.ATM 2 | { 3 | /// 4 | /// This overrides the default html report setting 5 | /// 6 | //public class AtmHtmlReportConfig : DefaultHtmlReportConfiguration 7 | //{ 8 | // public override bool RunsOn(Story story) 9 | // { 10 | // return story.Namespace.EndsWith("Atm", StringComparison.OrdinalIgnoreCase); 11 | // } 12 | 13 | // /// 14 | // /// Change the output file name 15 | // /// 16 | // public override string OutputFileName => "ATM.html"; 17 | 18 | // /// 19 | // /// Change the report header to your project 20 | // /// 21 | // public override string ReportHeader => "ATM Solutions"; 22 | 23 | // /// 24 | // /// Change the report description 25 | // /// 26 | // public override string ReportDescription => "A reliable solution for your offline banking needs"; 27 | 28 | // /// 29 | // /// For ATM report I want to embed jQuery in the report so people can see it with no internet connectivity 30 | // /// 31 | // public override bool ResolveJqueryFromCdn => false; 32 | //} 33 | } 34 | -------------------------------------------------------------------------------- /src/Samples/Specify.Samples/Specs/ATM/CardHasBeenDisabled.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using Specify.Samples.Domain.Atm; 3 | using TestStack.BDDfy; 4 | 5 | namespace Specify.Samples.Specs.ATM 6 | { 7 | public class CardHasBeenDisabled : ScenarioFor 8 | { 9 | private Card _card; 10 | 11 | public void Given_the_Card_is_disabled() 12 | { 13 | _card = new Card(false, 100); 14 | SUT = new Atm(100); 15 | } 16 | 17 | [When("When the account holder requests $20")] 18 | public void When_the_Account_Holder_requests_20_dollars() 19 | { 20 | SUT.RequestMoney(_card, 20); 21 | } 22 | 23 | public void Then_the_Card_should_be_retained() 24 | { 25 | SUT.CardIsRetained.ShouldBe(true); 26 | } 27 | 28 | public void AndThen_the_ATM_should_say_the_Card_has_been_retained() 29 | { 30 | SUT.Message.ShouldBe(DisplayMessage.CardIsRetained); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/Settings.StyleCop: -------------------------------------------------------------------------------- 1 | 2 | 3 | False 4 | 5 | -------------------------------------------------------------------------------- /src/app/Specify.Autofac/AutofacImmutableContainerException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Specify.Autofac 4 | { 5 | public class AutofacImmutableContainerException : Exception 6 | { 7 | public AutofacImmutableContainerException() 8 | : base(GetErrorMessage()) 9 | { 10 | } 11 | 12 | private static string GetErrorMessage() 13 | { 14 | return "Registering types in tests is not currently supported for Autofac since its Container became immutable from version 5.0. You can still register items in the Container in the Bootstrapper, but to register items in the Container from tests requires Specify.Autofac v3.0.0-beta01 or earlier and Autofac 4.9.4 or earlier."; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/app/Specify.Autofac/DefaultAutofacBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Specify.Configuration; 3 | 4 | namespace Specify.Autofac 5 | { 6 | public class DefaultAutofacBootstrapper : BootstrapperBase 7 | { 8 | protected override IContainer BuildApplicationContainer() 9 | { 10 | var builder = ConfigureContainerForApplication(); 11 | ConfigureContainerForSpecify(builder); 12 | var container = builder.Build(); 13 | return new AutofacContainer(container); 14 | } 15 | 16 | public virtual ContainerBuilder ConfigureContainerForApplication() 17 | { 18 | return new ContainerBuilder(); 19 | } 20 | 21 | public void ConfigureContainerForSpecify(ContainerBuilder builder) 22 | { 23 | builder.RegisterSpecify(MockFactory); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/app/Specify.Autofac/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("Specify.Autofac")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | [assembly: InternalsVisibleTo("Specify.IntegrationTests")] 14 | 15 | // Setting ComVisible to false makes the types in this assembly not visible 16 | // to COM components. If you need to access a type in this assembly from 17 | // COM, set the ComVisible attribute to true on that type. 18 | [assembly: ComVisible(false)] 19 | 20 | // The following GUID is for the ID of the typelib if this project is exposed to COM 21 | [assembly: Guid("66ad2b9c-5990-4bf2-8b3b-686637c31b28")] 22 | 23 | [assembly: AssemblyVersion("1.0.0.0")] 24 | [assembly: AssemblyInformationalVersion("3.0.0")] 25 | [assembly: AssemblyFileVersion("1.0.0.0")] 26 | -------------------------------------------------------------------------------- /src/app/Specify.Autofac/SpecifyAutofacConfigScanner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Specify.Configuration.Scanners; 3 | using Specify.Mocks; 4 | 5 | namespace Specify.Autofac 6 | { 7 | /// 8 | public class SpecifyAutofacConfigScanner : ConfigScanner 9 | { 10 | /// 11 | protected override Type DefaultBootstrapperType => typeof(DefaultAutofacBootstrapper); 12 | 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The file system. 17 | public SpecifyAutofacConfigScanner(IFileSystem fileSystem) 18 | : base(fileSystem) { } 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | public SpecifyAutofacConfigScanner() 24 | : this(new FileSystem()) { } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/Specify/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using TestStack.BDDfy.Configuration; 3 | 4 | namespace Specify 5 | { 6 | public static class Config 7 | { 8 | public static Func ScenarioTitle = scenario => 9 | { 10 | var className = scenario.GetType().FullName 11 | .Replace(scenario.GetType().Namespace + ".", string.Empty); 12 | var title = Configurator.Scanners 13 | .Humanize(className) 14 | .ToTitleCase(); 15 | if (scenario.Number != 0) 16 | { 17 | title = $"{scenario.Number:00}: {title}"; 18 | } 19 | return title; 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/Specify/Configuration/BddfyTestEngine.cs: -------------------------------------------------------------------------------- 1 | using TestStack.BDDfy; 2 | 3 | namespace Specify.Configuration 4 | { 5 | internal class BddfyTestEngine : ITestEngine 6 | { 7 | public Story Execute(IScenario scenario) 8 | { 9 | if (scenario.Examples == null) 10 | { 11 | return scenario.BDDfy(scenario.Title); 12 | } 13 | 14 | return scenario 15 | .WithExamples(scenario.Examples) 16 | .BDDfy(scenario.Title); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/app/Specify/Configuration/DefaultBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using DryIoc; 2 | using Specify.Containers; 3 | 4 | namespace Specify.Configuration 5 | { 6 | /// 7 | /// The startup class to configure Specify with the default TinyIoc container. 8 | /// Inherit from this class to change any of the default configuration settings. 9 | /// 10 | public class DefaultBootstrapper : BootstrapperBase 11 | { 12 | /// 13 | protected override IContainer BuildApplicationContainer() 14 | { 15 | var container = new DryContainerFactory().Create(MockFactory); 16 | ConfigureContainer(container); 17 | return new DryContainer(container); 18 | } 19 | 20 | /// 21 | /// Register any additional items into the DryIoc container. 22 | /// 23 | /// The DryIoc container. 24 | public virtual void ConfigureContainer(Container container) 25 | { 26 | 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/Specify/Configuration/Examples/IExampleScope.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Configuration.Examples 2 | { 3 | public interface IExampleScope 4 | { 5 | void BeginScope(IScenario scenario) 6 | where T : class; 7 | 8 | void EndScope(IScenario scenario) 9 | where T : class; 10 | } 11 | } -------------------------------------------------------------------------------- /src/app/Specify/Configuration/Examples/NullExampleScope.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Configuration.Examples 2 | { 3 | class NullExampleScope : IExampleScope 4 | { 5 | public void BeginScope(IScenario scenario) where T : class 6 | { 7 | 8 | } 9 | 10 | public void EndScope(IScenario scenario) where T : class 11 | { 12 | 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/app/Specify/Configuration/ExecutableAttributes/BeginTestCaseAttribute.cs: -------------------------------------------------------------------------------- 1 | using TestStack.BDDfy; 2 | 3 | namespace Specify.Configuration.ExecutableAttributes 4 | { 5 | /// 6 | /// Will run before every BDDfy method for each test case (scenario or example) 7 | /// 8 | public class BeginTestCaseAttribute : ExecutableAttribute 9 | { 10 | public BeginTestCaseAttribute() : base(ExecutionOrder.Initialize, null) 11 | { 12 | ShouldReport = false; 13 | Order = int.MinValue; // Forces running before any other ExecutionOrder.Initialize method 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/app/Specify/Configuration/ExecutableAttributes/EndTestCaseAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using TestStack.BDDfy; 3 | 4 | namespace Specify.Configuration.ExecutableAttributes 5 | { 6 | /// 7 | /// Will run after every BDDfy method for each test case (scenario or example) 8 | /// 9 | public class EndTestCaseAttribute : ExecutableAttribute 10 | { 11 | public EndTestCaseAttribute() : base(ExecutionOrder.TearDown, null) 12 | { 13 | ShouldReport = false; 14 | Order = int.MaxValue; // Forces running after any other ExecutionOrder.TearDown method 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/Specify/Configuration/IBootstrapSpecify.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Specify.Mocks; 3 | 4 | namespace Specify.Configuration 5 | { 6 | /// 7 | /// Interface IBootstrapSpecify 8 | /// 9 | public interface IBootstrapSpecify 10 | { 11 | /// 12 | /// Gets the container that will be used for the lifetime of the application. 13 | /// 14 | /// The application container. 15 | IContainer ApplicationContainer { get; } 16 | 17 | /// 18 | /// Gets the mock provider if one is referenced. 19 | /// 20 | /// The mock factory. 21 | IMockFactory MockFactory { get; set; } 22 | 23 | /// 24 | /// Gets or sets a value indicating whether logging is enabled. 25 | /// 26 | /// true if logging is enabled otherwise false. 27 | bool LoggingEnabled { get; set; } 28 | 29 | /// 30 | /// The configuration values for the BDDfy HTML report. 31 | /// 32 | /// The report header. 33 | HtmlReportConfiguration HtmlReport { get; } 34 | 35 | /// 36 | /// Initializes Specify before any scenarios are run. 37 | /// 38 | void InitializeSpecify(); 39 | } 40 | } -------------------------------------------------------------------------------- /src/app/Specify/Configuration/IPerApplicationAction.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Configuration 2 | { 3 | /// 4 | /// Represents an action to be performed once per Appliction (before and after all scenarios are run). 5 | /// 6 | public interface IPerApplicationAction 7 | { 8 | /// 9 | /// Action to be performed before any scenarios have run. 10 | /// 11 | void Before(); 12 | 13 | /// 14 | /// Action to be performed after all scenarios have run. 15 | /// 16 | void After(); 17 | 18 | /// 19 | /// The order the action will run in. 20 | /// 21 | int Order { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /src/app/Specify/Configuration/IPerScenarioAction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Specify.Configuration 4 | { 5 | /// 6 | /// Represents an action to be performed before and after each scenario. 7 | /// 8 | public interface IPerScenarioAction 9 | { 10 | /// 11 | /// Action to be performed before each scenario. 12 | /// 13 | void Before(IScenario scenario) where TSut : class; 14 | 15 | /// 16 | /// Action to be performed after each scenario. 17 | /// 18 | void After(); 19 | 20 | /// 21 | /// Whether this action should be performed for a particular type. 22 | /// 23 | bool ShouldExecute(Type type); 24 | 25 | /// 26 | /// The order the action will run in. 27 | /// 28 | int Order { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/Specify/Configuration/ITestEngine.cs: -------------------------------------------------------------------------------- 1 | using TestStack.BDDfy; 2 | 3 | namespace Specify.Configuration 4 | { 5 | 6 | /// 7 | /// Represents the class that exectues scenarios 8 | /// 9 | public interface ITestEngine 10 | { 11 | /// 12 | /// Executes the specified scenario. 13 | /// 14 | /// The scenario. 15 | /// Story. 16 | Story Execute(IScenario scenario); 17 | } 18 | } -------------------------------------------------------------------------------- /src/app/Specify/Configuration/ReportConfiguration.cs: -------------------------------------------------------------------------------- 1 | using TestStack.BDDfy.Reporters.Html; 2 | 3 | namespace Specify.Configuration 4 | { 5 | /// 6 | /// Configuration for the BDDfy HTML report. 7 | /// 8 | public class HtmlReportConfiguration : DefaultHtmlReportConfiguration 9 | { 10 | /// 11 | /// Gets or sets the type of the report. 12 | /// 13 | /// The type of the report. 14 | public HtmlReportType ReportType { get; set; } = HtmlReportType.Classic; 15 | 16 | /// 17 | /// Enum HtmlReportType 18 | /// 19 | public enum HtmlReportType 20 | { 21 | /// 22 | /// The classic BDDfy HTML report 23 | /// 24 | Classic, 25 | 26 | /// 27 | /// The newer metro BDDfy HTML report 28 | /// 29 | Metro 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/app/Specify/Configuration/Scanners/ConfigScannerFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Specify.Mocks; 3 | 4 | namespace Specify.Configuration.Scanners 5 | { 6 | /// 7 | /// Creates the appropriate ConfigScanner, depending on whether Specify if being used alone or with a plugin. 8 | /// 9 | public class ConfigScannerFactory 10 | { 11 | /// 12 | /// Chooses the configuration scanner. Specify itself, and each plugin, has its own configuration scanner. 13 | /// If Specify is being used alone its configuration scanner is used. If a plugin is installed its configuration scanner is used. 14 | /// 15 | /// IConfigScanner. 16 | public static IConfigScanner SelectScanner(IFileSystem fileSystem = null) 17 | { 18 | fileSystem = fileSystem ?? new FileSystem(); 19 | var scanners = fileSystem 20 | .GetAllTypesFromAppDomain() 21 | .Where(type => type.IsConcreteTypeOf()) 22 | .ToList(); 23 | 24 | return scanners.Count() == 1 25 | ? scanners.First().Create() 26 | : scanners 27 | .Except(new[] {typeof (SpecifyConfigScanner)}) 28 | .First() 29 | .Create(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/app/Specify/Configuration/Scanners/IConfigScanner.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Configuration.Scanners 2 | { 3 | /// 4 | /// Scans the AppDomain to find the appropriate bootstrapper class. 5 | /// 6 | public interface IConfigScanner 7 | { 8 | /// 9 | /// Scans the AppDomain to find the appropriate bootstrapper class. 10 | /// 11 | /// IBootstrapSpecify. 12 | IBootstrapSpecify GetConfiguration(); 13 | } 14 | } -------------------------------------------------------------------------------- /src/app/Specify/Configuration/Scanners/SpecifyConfigScanner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Specify.Mocks; 3 | 4 | namespace Specify.Configuration.Scanners 5 | { 6 | /// 7 | public class SpecifyConfigScanner : ConfigScanner 8 | { 9 | /// 10 | protected override Type DefaultBootstrapperType => typeof (DefaultBootstrapper); 11 | 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The file system. 16 | public SpecifyConfigScanner(IFileSystem fileSystem) 17 | : base(fileSystem) { } 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | public SpecifyConfigScanner() 23 | : this(new FileSystem()) { } 24 | } 25 | } -------------------------------------------------------------------------------- /src/app/Specify/Configuration/StepScanners/ArgumentCleaningExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Specify.Configuration.StepScanners 6 | { 7 | internal static class ArgumentCleaningExtensions 8 | { 9 | internal static object[] FlattenArrays(this IEnumerable inputs) 10 | { 11 | return inputs.Select(FlattenArray).ToArray(); 12 | } 13 | 14 | public static object FlattenArray(this object input) 15 | { 16 | var inputArray = input as Array; 17 | if (inputArray != null) 18 | { 19 | var temp = (from object arrElement in inputArray select GetSafeString(arrElement)).ToArray(); 20 | return string.Join(", ", temp); 21 | } 22 | 23 | if (input == null) return "'null'"; 24 | 25 | return input; 26 | } 27 | 28 | static string GetSafeString(object input) 29 | { 30 | if (input == null) 31 | return "'null'"; 32 | 33 | return input.ToString(); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/app/Specify/Exceptions/InterfaceRegistrationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Specify.Logging; 3 | 4 | namespace Specify.Exceptions 5 | { 6 | /// 7 | /// The exception for errors when registering objects to the scenario container. 8 | /// 9 | public class InterfaceRegistrationException : Exception 10 | { 11 | /// 12 | /// The formatted exception message. 13 | /// 14 | public const string ExceptionMessageFormat = "Cannot register service {0} after SUT is created"; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The service that could not be registered for some reason. 20 | public InterfaceRegistrationException(Type serviceThatCouldNotBeRegisteredForSomeReason) 21 | : base(string.Format(ExceptionMessageFormat, serviceThatCouldNotBeRegisteredForSomeReason.FullName)) 22 | { 23 | this.Log().ErrorFormat(ExceptionMessageFormat, serviceThatCouldNotBeRegisteredForSomeReason.FullName); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/app/Specify/Exceptions/LoggingException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Specify.Exceptions 4 | { 5 | /// 6 | /// The exception for errors during logging. 7 | /// 8 | public class LoggingException : Exception 9 | { 10 | /// 11 | /// The formatted exception message. 12 | /// 13 | public const string ExceptionMessageFormat = "Failed to log for {0} logger."; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The inner exception. 19 | /// The logger that could not log for some reason. 20 | public LoggingException(Exception innerException, string loggerThatCouldNotLogForSomeReason) 21 | : base(string.Format(ExceptionMessageFormat, loggerThatCouldNotLogForSomeReason), innerException) 22 | { 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/app/Specify/Extensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Specify 6 | { 7 | public static class EnumerableExtensions 8 | { 9 | public static IEnumerable Each(this IEnumerable enumerable, Action action) 10 | { 11 | var items = enumerable as T[] ?? enumerable.ToArray(); 12 | foreach (var item in items) 13 | { 14 | action(item); 15 | } 16 | 17 | return items; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/app/Specify/Extensions/ExceptionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace Specify 5 | { 6 | public static class ExceptionExtensions 7 | { 8 | public static string UnwrapInnerExceptionMessages(this Exception exception) 9 | { 10 | var sb = new StringBuilder(); 11 | 12 | if (!string.IsNullOrWhiteSpace(exception.Message)) 13 | { 14 | sb.AppendLine(exception.Message); 15 | } 16 | 17 | if (exception.InnerException != null) 18 | { 19 | sb = GetInnerExceptionMessage(sb, exception.InnerException); 20 | } 21 | 22 | return sb.ToString(); 23 | } 24 | 25 | private static StringBuilder GetInnerExceptionMessage(StringBuilder sb, Exception exception) 26 | { 27 | if (!string.IsNullOrWhiteSpace(exception.Message)) 28 | { 29 | sb.AppendLine(exception.Message); 30 | } 31 | 32 | if (exception.InnerException != null) 33 | { 34 | sb = GetInnerExceptionMessage(sb, exception.InnerException); 35 | } 36 | 37 | return sb; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/Specify/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Specify 5 | { 6 | public static class StringExtensions 7 | { 8 | public static string ToTitleCase(this string input) 9 | { 10 | var words = input.Split(' '); 11 | var result = new List(); 12 | foreach (var word in words) 13 | { 14 | if (word.Length == 0 || AllCapitals(word)) 15 | result.Add(word); 16 | else if (word.Length == 1) 17 | result.Add(word.ToUpper()); 18 | else 19 | result.Add(char.ToUpper(word[0]) + word.Remove(0, 1).ToLower()); 20 | } 21 | 22 | return string.Join(" ", result); 23 | } 24 | 25 | static bool AllCapitals(string input) 26 | { 27 | return input.ToCharArray().All(char.IsUpper); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/Specify/Host.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Specify.Configuration; 3 | using Specify.Configuration.Scanners; 4 | using Specify.Logging; 5 | 6 | namespace Specify 7 | { 8 | public static class Host 9 | { 10 | public static readonly IBootstrapSpecify Configuration; 11 | private static readonly ScenarioRunner _scenarioRunner; 12 | 13 | internal static void Specify(IScenario testObject, string scenarioTitle = null) where TSut : class 14 | { 15 | _scenarioRunner.Execute(testObject, scenarioTitle); 16 | } 17 | 18 | static Host() 19 | { 20 | "Host".Log().DebugFormat("Registering System.Runtime.Loader.AssemblyLoadContext Unloading event"); 21 | System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += context => _scenarioRunner.AfterAllScenarios(); 22 | 23 | var scanner = ConfigScannerFactory.SelectScanner(); 24 | Configuration = scanner.GetConfiguration(); 25 | 26 | _scenarioRunner = new ScenarioRunner(Configuration, new BddfyTestEngine()); 27 | _scenarioRunner.BeforeAllScenarios(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/app/Specify/Mocks/AutoMockerFor.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Mocks 2 | { 3 | /// 4 | /// An auto mocking container with NSubstitute, Moq or FakeItEasy. 5 | /// 6 | /// The type of the t sut. 7 | public class AutoMockerFor : ContainerFor 8 | where TSut : class 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | public AutoMockerFor() 14 | : base(CreateContainer()) { } 15 | 16 | private static IContainer CreateContainer() 17 | { 18 | return Host.Configuration.ApplicationContainer.Get(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/app/Specify/Mocks/FakeItEasyMockFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Specify.Mocks 4 | { 5 | /// 6 | /// Adapter for the FakeItEasy mocking provider. 7 | /// 8 | public class FakeItEasyMockFactory : MockFactoryBase 9 | { 10 | /// 11 | public FakeItEasyMockFactory() 12 | : base(new FileSystem()) { } 13 | 14 | /// 15 | public FakeItEasyMockFactory(IFileSystem fileSystem) 16 | : base(fileSystem) { } 17 | 18 | /// 19 | protected override string MockTypeName => "FakeItEasy.A"; 20 | 21 | /// 22 | public override object CreateMock(Type type) 23 | { 24 | var openFakeMethod = MockOpenType.GetMethodInfo("Fake", Type.EmptyTypes); 25 | var closedFakeMethod = openFakeMethod.MakeGenericMethod(type); 26 | 27 | return closedFakeMethod.Invoke(null, null); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/app/Specify/Mocks/FileSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Reflection; 5 | using Specify.lib; 6 | 7 | namespace Specify.Mocks 8 | { 9 | /// 10 | /// Wrapper around file system and reflection methods. 11 | /// 12 | public class FileSystem : IFileSystem 13 | { 14 | /// 15 | public bool IsAssemblyAvailable(string assemblyName) 16 | { 17 | try 18 | { 19 | Assembly.Load(new AssemblyName(assemblyName)); 20 | return true; 21 | } 22 | catch (FileNotFoundException) 23 | { 24 | return false; 25 | } 26 | } 27 | 28 | /// 29 | public Assembly Load(string assemblyName) 30 | { 31 | return Assembly.Load(new AssemblyName(assemblyName)); 32 | } 33 | 34 | /// 35 | public Type GetTypeFrom(Assembly assembly, string typeName) 36 | { 37 | return assembly.GetType(typeName); 38 | } 39 | 40 | /// 41 | public IEnumerable GetAllTypesFromAppDomain() 42 | { 43 | return AssemblyTypeResolver.GetAllTypesFromAppDomain(); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/app/Specify/Mocks/IMockFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Specify.Mocks 4 | { 5 | /// 6 | /// The contract for the different mock providers to implement to create a mock object. 7 | /// 8 | public interface IMockFactory 9 | { 10 | /// 11 | /// The name of the mocking framework that the factory uses. 12 | /// 13 | /// Mocking framework name 14 | string MockProviderName { get; } 15 | 16 | /// 17 | /// Creates the mock. 18 | /// 19 | /// The type to be mocked. 20 | /// System.Object. 21 | object CreateMock(Type type); 22 | 23 | /// 24 | /// Determines whether the mock provider assembly is registered. 25 | /// 26 | /// true if [is assembly available]; otherwise, false. 27 | bool IsProviderAvailable { get; } 28 | } 29 | } -------------------------------------------------------------------------------- /src/app/Specify/Mocks/MoqMockFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Specify.Mocks 4 | { 5 | /// 6 | /// Adapter for the Moq mocking provider. 7 | /// 8 | public class MoqMockFactory : MockFactoryBase 9 | { 10 | /// 11 | public MoqMockFactory() 12 | : base(new FileSystem()) { } 13 | 14 | /// 15 | public MoqMockFactory(IFileSystem fileSystem) 16 | : base(fileSystem) { } 17 | 18 | /// 19 | protected override string MockTypeName => "Moq.Mock`1"; 20 | 21 | /// 22 | public override object CreateMock(Type type) 23 | { 24 | var closedType = MockOpenType.MakeGenericType(new[] {type}); 25 | var objectProperty = closedType.GetPropertyInfo("Object", type); 26 | var instance = closedType.Create(); 27 | 28 | return objectProperty.GetValue(instance, null); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/app/Specify/Mocks/NSubstituteMockFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace Specify.Mocks 5 | { 6 | /// 7 | /// Adapter for the NSubstitute mocking provider. 8 | /// 9 | public class NSubstituteMockFactory : MockFactoryBase 10 | { 11 | /// 12 | public NSubstituteMockFactory() 13 | : base(new FileSystem()) { } 14 | 15 | /// 16 | public NSubstituteMockFactory(IFileSystem fileSystem) 17 | : base(fileSystem) { } 18 | 19 | /// 20 | protected override string MockTypeName => "NSubstitute.Substitute"; 21 | 22 | /// 23 | public override object CreateMock(Type type) 24 | { 25 | var methods = MockOpenType.GetMethods(); 26 | foreach (var method in methods) 27 | { 28 | if (IsGenericForMethod(method)) 29 | { 30 | var closedGenericForMethod = method.MakeGenericMethod(type); 31 | return closedGenericForMethod.Invoke(null, new object[1] { null }); 32 | } 33 | } 34 | return null; 35 | } 36 | 37 | private static bool IsGenericForMethod(MethodInfo method) 38 | { 39 | return method.ContainsGenericParameters && method.GetGenericArguments().Length == 1; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/app/Specify/Mocks/NullMockFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Specify.Mocks 4 | { 5 | /// 6 | /// Null mock factory adapter. 7 | /// 8 | public class NullMockFactory : IMockFactory 9 | { 10 | public string MockProviderName => "No mock factory detected"; 11 | 12 | public object CreateMock(Type type) 13 | { 14 | throw new NotImplementedException(); 15 | } 16 | 17 | public bool IsProviderAvailable => true; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/Specify/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("Specify")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | [assembly: InternalsVisibleTo("Specify.Tests")] 14 | [assembly: InternalsVisibleTo("Specify.IntegrationTests")] 15 | [assembly: InternalsVisibleTo("Specify.Autofac")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("edf7d294-e262-4089-97d9-e105de42172d")] 24 | 25 | [assembly: AssemblyVersion("1.0.0.0")] 26 | [assembly: AssemblyInformationalVersion("3.0.0")] 27 | [assembly: AssemblyFileVersion("1.0.0.0")] 28 | -------------------------------------------------------------------------------- /src/app/Specify/Stories/SpecificationStory.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Stories 2 | { 3 | /// 4 | /// The BDDfy Story class used for unit test specifications. 5 | /// 6 | public class SpecificationStory : Story 7 | { 8 | /// 9 | /// Initializes a new instance of the class. 10 | /// 11 | public SpecificationStory() 12 | { 13 | TitlePrefix = "Specifications For: "; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/Specify/Stories/UserStory.cs: -------------------------------------------------------------------------------- 1 | namespace Specify.Stories 2 | { 3 | /// 4 | /// A User Story template base class for a BDDfy Story allowing representation as a class rather than an attribute. 5 | /// 6 | public abstract class UserStory : Story 7 | { 8 | private const string As_a_prefix = "As a"; 9 | private const string I_want_prefix = "I want"; 10 | private const string So_that_prefix = "So that"; 11 | 12 | /// 13 | /// Gets or sets the 'As a' clause. 14 | /// 15 | /// As a. 16 | public string AsA 17 | { 18 | get { return Narrative1; } 19 | set { Narrative1 = CleanseProperty(value, As_a_prefix); } 20 | } 21 | 22 | /// 23 | /// Gets or sets the 'I want' clause. 24 | /// 25 | /// The i want. 26 | public string IWant 27 | { 28 | get { return Narrative2; } 29 | set { Narrative2 = CleanseProperty(value, I_want_prefix); } 30 | } 31 | 32 | /// 33 | /// Gets or sets the 'So that' clause. 34 | /// 35 | /// The so that. 36 | public string SoThat 37 | { 38 | get { return Narrative3; } 39 | set { Narrative3 = CleanseProperty(value, So_that_prefix); } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/tests/Specify.IntegrationTests/ContainerFors/AutoMocking/AutoMockerContainerForTests.cs: -------------------------------------------------------------------------------- 1 | using Specify.Mocks; 2 | 3 | namespace Specify.IntegrationTests.ContainerFors.AutoMocking 4 | { 5 | public class AutoMockerContainerForGetTests : ContainerForGetTestsBase 6 | { 7 | protected override ContainerFor CreateSut() 8 | { 9 | return new AutoMockerFor(); 10 | } 11 | } 12 | 13 | public class AutoMockerContainerForSetTests : ContainerForSetTestsBase 14 | { 15 | protected override ContainerFor CreateSut() 16 | { 17 | return new AutoMockerFor(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/tests/Specify.IntegrationTests/ContainerFors/AutoMocking/AutofacMockingContainerForTests.cs: -------------------------------------------------------------------------------- 1 | using Specify.IntegrationTests.Containers; 2 | using Specify.Mocks; 3 | 4 | namespace Specify.IntegrationTests.ContainerFors.AutoMocking 5 | { 6 | public abstract class AutofacMockingContainerForGetTests : ContainerForGetTestsBase 7 | where TMockFactory : IMockFactory 8 | { 9 | protected override ContainerFor CreateSut() 10 | { 11 | var container = ContainerFactory.CreateAutofacContainer(); 12 | return new ContainerFor(container); 13 | } 14 | } 15 | 16 | public class AutofacNSubstituteContainerForGetTests 17 | : AutofacMockingContainerForGetTests { } 18 | 19 | public class AutofacMoqContainerForGetTests 20 | : AutofacMockingContainerForGetTests { } 21 | 22 | public class AutofacFakeItEasyContainerForGetTests 23 | : AutofacMockingContainerForGetTests { } 24 | 25 | } -------------------------------------------------------------------------------- /src/tests/Specify.IntegrationTests/ContainerFors/Ioc/AutofacContainerForGetTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Autofac; 3 | using Specify.Autofac; 4 | using Specify.IntegrationTests.Containers; 5 | using Specify.Mocks; 6 | using Specify.Tests.Stubs; 7 | 8 | namespace Specify.IntegrationTests.ContainerFors.Ioc 9 | { 10 | public class AutofacContainerForGetTests : ContainerForGetTestsBase 11 | { 12 | protected override ContainerFor CreateSut() 13 | { 14 | Action registrations = builder => 15 | { 16 | builder.RegisterType().As(); 17 | builder.RegisterType().As(); 18 | 19 | builder.RegisterType().As(); 20 | builder.RegisterType().As(); 21 | builder.RegisterType(); 22 | builder.RegisterType(); 23 | builder.RegisterType(); 24 | }; 25 | 26 | var container = ContainerFactory.CreateAutofacContainer(registrations); 27 | 28 | return new ContainerFor(container); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/tests/Specify.IntegrationTests/Containers/AutoMocking/AutofacMockingContainerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Autofac; 3 | using Specify.Autofac; 4 | using Specify.Mocks; 5 | using Specify.Tests.Stubs; 6 | 7 | namespace Specify.IntegrationTests.Containers.AutoMocking 8 | { 9 | public abstract class AutofacMockingContainerTestsFor : MockingContainerGetTestsFor 10 | where TMockFactory : IMockFactory 11 | { 12 | protected override AutofacContainer CreateSut() 13 | { 14 | Action registrations = builder => builder.RegisterType(); 15 | return ContainerFactory.CreateAutofacContainer(registrations); 16 | } 17 | } 18 | 19 | public class AutofacNSubstituteMockingContainerGetTests 20 | : AutofacMockingContainerTestsFor { } 21 | 22 | public class AutofacMoqMockingContainerGetTests 23 | : AutofacMockingContainerTestsFor { } 24 | 25 | public class AutofacFakeItEasyMockingContainerGetTests 26 | : AutofacMockingContainerTestsFor { } 27 | } 28 | -------------------------------------------------------------------------------- /src/tests/Specify.IntegrationTests/Containers/ContainerFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Autofac; 3 | using DryIoc; 4 | using Specify.Autofac; 5 | using Specify.Containers; 6 | using Specify.Mocks; 7 | 8 | namespace Specify.IntegrationTests.Containers 9 | { 10 | public static class ContainerFactory 11 | { 12 | public static AutofacContainer CreateAutofacContainer(Action action = null) 13 | { 14 | action = action ?? (builder => { }); 15 | 16 | var mockFactoryInstance = typeof(TMockFactory).Create(); 17 | var builder = new ContainerBuilder(); 18 | builder.RegisterSpecify(mockFactoryInstance, false); 19 | action(builder); 20 | var container = builder.Build(); 21 | 22 | return new AutofacContainer(container); 23 | } 24 | 25 | public static DryContainer CreateDryContainer(Action action = null) 26 | { 27 | action = action ?? (builder => { }); 28 | 29 | var mockFactoryInstance = typeof(TMockFactory).Create(); 30 | var container = new DryContainerFactory().Create(mockFactoryInstance); 31 | action(container); 32 | 33 | return new DryContainer(container); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/tests/Specify.IntegrationTests/Containers/Ioc/Application/AutofacApplicationContainerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Autofac; 3 | using Specify.Autofac; 4 | using Specify.Mocks; 5 | using Specify.Tests.Stubs; 6 | 7 | namespace Specify.IntegrationTests.Containers.Ioc.Application 8 | { 9 | //public class AutofacApplicationContainerTests : ApplicationContainerTestsFor 10 | //{ 11 | // protected override AutofacContainer CreateSut() 12 | // { 13 | // Action registrations = builder => builder.RegisterType(); 14 | // return ContainerFactory.CreateAutofacContainer(registrations); 15 | // } 16 | //} 17 | } -------------------------------------------------------------------------------- /src/tests/Specify.IntegrationTests/Containers/Ioc/Application/DryApplicationContainerTests.cs: -------------------------------------------------------------------------------- 1 | using Specify.Containers; 2 | using Specify.Mocks; 3 | 4 | namespace Specify.IntegrationTests.Containers.Ioc.Application 5 | { 6 | public class DryApplicationContainerTests : ApplicationContainerTestsFor 7 | { 8 | protected override DryContainer CreateSut() 9 | { 10 | var container = new DryContainerFactory().Create(new NullMockFactory()); 11 | return new DryContainer(container); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/tests/Specify.IntegrationTests/Containers/Ioc/AutofacContainerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Autofac; 3 | using Specify.Autofac; 4 | using Specify.Mocks; 5 | using Specify.Tests.Stubs; 6 | 7 | namespace Specify.IntegrationTests.Containers.Ioc 8 | { 9 | public class AutofacContainerTests : IocContainerGetTestsFor 10 | { 11 | protected override AutofacContainer CreateSut() 12 | { 13 | Action registrations = builder => builder.RegisterType(); 14 | return ContainerFactory.CreateAutofacContainer(registrations); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/tests/Specify.IntegrationTests/Containers/Ioc/DryContainerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DryIoc; 3 | using Specify.Containers; 4 | using Specify.Mocks; 5 | using Specify.Tests.Stubs; 6 | 7 | namespace Specify.IntegrationTests.Containers.Ioc 8 | { 9 | public class DryContainerGetTests : IocContainerGetTestsFor 10 | { 11 | protected override DryContainer CreateSut() 12 | { 13 | Action registrations = container => container.Register(); 14 | return ContainerFactory.CreateDryContainer(registrations); 15 | } 16 | } 17 | 18 | public class DryContainerSetTests : IocContainerSetTestsFor 19 | { 20 | protected override DryContainer CreateSut() 21 | { 22 | Action registrations = container => container.Register(); 23 | return ContainerFactory.CreateDryContainer(registrations); 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/tests/Specify.IntegrationTests/Mocks/FakeItEasyMockFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using FakeItEasy; 5 | using NSubstitute; 6 | using NUnit.Framework; 7 | using Shouldly; 8 | using Specify.Mocks; 9 | using Specify.Tests.Stubs; 10 | 11 | namespace Specify.IntegrationTests.Mocks 12 | { 13 | public class FakeItEasyMockFactoryTests : MockFactoryTestsFor 14 | { 15 | [Test] 16 | public void should_return_fake() 17 | { 18 | var sut = CreateSut(); 19 | var fake = (IDependency1)sut.CreateMock(typeof(IDependency1)); 20 | 21 | A.CallTo(() => fake.Value).Returns(11); 22 | 23 | var result = new ConcreteObjectWithOneInterfaceConstructor(fake).Dependency1.Value; 24 | 25 | result.ShouldBe(11); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/tests/Specify.IntegrationTests/Mocks/FileSystemTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Shouldly; 3 | using Specify.Mocks; 4 | 5 | namespace Specify.IntegrationTests.Mocks 6 | { 7 | [TestFixture] 8 | public class FileSystemTests 9 | { 10 | [Test] 11 | public void should_return_true_if_assembly_available() 12 | { 13 | var sut = new FileSystem(); 14 | sut.IsAssemblyAvailable("NSubstitute") 15 | .ShouldBe(true); 16 | } 17 | 18 | [Test] 19 | public void should_return_false_if_assembly_not_available() 20 | { 21 | var sut = new FileSystem(); 22 | sut.IsAssemblyAvailable("blah") 23 | .ShouldBe(false); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/tests/Specify.IntegrationTests/Mocks/MockFactoryTestsFor.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Shouldly; 3 | using Specify.Mocks; 4 | using Specify.Tests.Stubs; 5 | 6 | namespace Specify.IntegrationTests.Mocks 7 | { 8 | [TestFixture] 9 | public abstract class MockFactoryTestsFor where T : IMockFactory, new() 10 | { 11 | public IMockFactory CreateSut() 12 | { 13 | return new T(); 14 | } 15 | 16 | [Test] 17 | public void should_mock_interfaces() 18 | { 19 | var sut = this.CreateSut(); 20 | var result = sut.CreateMock(typeof (IDependency1)); 21 | 22 | result.ShouldNotBe(null); 23 | result.ShouldBeAssignableTo(); 24 | } 25 | 26 | [Test] 27 | public void should_mock_concrete() 28 | { 29 | var sut = this.CreateSut(); 30 | var result = sut.CreateMock(typeof(Dependency1)); 31 | 32 | result.ShouldNotBe(null); 33 | result.ShouldBeAssignableTo(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/tests/Specify.IntegrationTests/Mocks/MoqMockFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using NUnit.Framework; 3 | using Shouldly; 4 | using Specify.Mocks; 5 | using Specify.Tests.Stubs; 6 | 7 | namespace Specify.IntegrationTests.Mocks 8 | { 9 | public class MoqMockFactoryTests : MockFactoryTestsFor 10 | { 11 | [Test] 12 | public void should_return_mock() 13 | { 14 | var sut = CreateSut(); 15 | var mock = (IDependency1)sut.CreateMock(typeof(IDependency1)); 16 | Mock.Get(mock).Setup(x => x.Value).Returns(11); 17 | 18 | var result = new ConcreteObjectWithOneInterfaceConstructor(mock).Dependency1.Value; 19 | 20 | result.ShouldBe(11); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/tests/Specify.IntegrationTests/Mocks/NSubstituteMockFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using NSubstitute; 2 | using NUnit.Framework; 3 | using Shouldly; 4 | using Specify.Mocks; 5 | using Specify.Tests.Stubs; 6 | 7 | namespace Specify.IntegrationTests.Mocks 8 | { 9 | public class NSubstituteMockFactoryTests : MockFactoryTestsFor 10 | { 11 | [Test] 12 | public void should_return_substitute() 13 | { 14 | var sut = CreateSut(); 15 | var substitute = (IDependency1)sut.CreateMock(typeof(IDependency1)); 16 | substitute.Value.Returns(11); 17 | 18 | var result = new ConcreteObjectWithOneInterfaceConstructor(substitute).Dependency1.Value; 19 | 20 | result.ShouldBe(11); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/tests/Specify.IntegrationTests/Specify.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | false 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | all 25 | runtime; build; native; contentfiles; analyzers; buildtransitive 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/tests/Specify.Tests/Apis/SemanticVersioningTests.cs: -------------------------------------------------------------------------------- 1 | //using NUnit.Framework; 2 | //using Shouldly; 3 | //using Specify.Autofac; 4 | 5 | //namespace Specify.Tests.Apis 6 | //{ 7 | // public class SemanticVersioningTests 8 | // { 9 | //#if NET46 10 | // [Test] 11 | // public void specify_has_no_public_api_changes() 12 | // { 13 | // var publicApi = PublicApiGenerator.ApiGenerator.GeneratePublicApi(typeof(IScenario).Assembly); 14 | // publicApi.ShouldMatchApproved(); 15 | // } 16 | 17 | // [Test] 18 | // public void specify_autofac_has_no_public_api_changes() 19 | // { 20 | // var publicApi = PublicApiGenerator.ApiGenerator.GeneratePublicApi(typeof(AutofacContainer).Assembly); 21 | // publicApi.ShouldMatchApproved(); 22 | // } 23 | //#endif 24 | // } 25 | //} 26 | -------------------------------------------------------------------------------- /src/tests/Specify.Tests/Configuration/BddfyTestEngineTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NSubstitute; 3 | using NUnit.Framework; 4 | using Shouldly; 5 | using Specify.Configuration; 6 | using Specify.Tests.Stubs; 7 | using TestStack.BDDfy; 8 | using TestStack.BDDfy.Reporters; 9 | 10 | namespace Specify.Tests.Configuration 11 | { 12 | [TestFixture] 13 | public class BddfyTestEngineTests 14 | { 15 | [Test] 16 | public void should_handle_specification_with_examples() 17 | { 18 | var spec = new StubUserStoryScenarioForWithExamples(); 19 | var sut = new BddfyTestEngine(); 20 | 21 | sut.Execute(spec); 22 | 23 | spec.ExamplesWasCalled.ShouldBe(2); 24 | } 25 | 26 | [Test] 27 | public void should_handle_specification_without_examples() 28 | { 29 | var spec = Substitute.For(); 30 | var sut = new BddfyTestEngine(); 31 | 32 | sut.Execute(spec); 33 | 34 | spec.Received().BDDfy(); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/tests/Specify.Tests/Configuration/BddfyTestEngineTests.should_display_examples_on_reports.approved.txt: -------------------------------------------------------------------------------- 1 |  2 | Scenario: Stub User Story Scenario For With Examples 3 | Given step with passed as parameter 4 | Given step with accessed via property 5 | 6 | Examples: 7 | | First Example | Second Example | 8 | | 1 | foo | 9 | | 2 | bar | 10 | 11 | -------------------------------------------------------------------------------- /src/tests/Specify.Tests/Configuration/LoggingProcessorTests.LoggingConfigurationTest.approved.txt: -------------------------------------------------------------------------------- 1 | 2014-03-25 11:30:05.0000|DEBUG|Specify.DefaultApplicationContainer|Registered 8 Scenarios 2 | 2014-03-25 11:30:05.0000|DEBUG|Specify.DefaultApplicationContainer|Registered DefaultAutoMockingContainer for IScenarioContainer with mock factory NSubstituteMockFactory 3 | 2014-03-25 11:30:05.0000|DEBUG|TestRunner|Bootstrapper: Specify.Configuration.SpecifyBootstrapper 4 | 2014-03-25 11:30:05.0000|DEBUG|TestRunner|ApplicationContainer: Specify.DefaultApplicationContainer 5 | 2014-03-25 11:30:05.0000|DEBUG|TestRunner|ScenarioContainer: Specify.DefaultScenarioContainer 6 | 2014-03-25 11:30:05.0000|DEBUG|TestRunner|PerAppDomainActions: 0 7 | 2014-03-25 11:30:05.0000|DEBUG|TestRunner|PerScenarioActions: 0 8 | 2014-03-25 11:30:05.0000|DEBUG|TestRunner|Logging Enabled = False. 9 | -------------------------------------------------------------------------------- /src/tests/Specify.Tests/Configuration/LoggingProcessorTests.LoggingOutputTest.approved.txt: -------------------------------------------------------------------------------- 1 | 2014-03-25 11:30:05.0000|INFO|Specify|Scenario: Example Scenario Result: Passed Duration: 15 milliseconds. 2 | 2014-03-25 11:30:05.0000|DEBUG|Specify|Step: Given a account balance Result: Passed Duration: 5 milliseconds. 3 | 2014-03-25 11:30:05.0000|DEBUG|Specify|Step: When the account holder requests money Result: Passed Duration: 5 milliseconds. 4 | 2014-03-25 11:30:05.0000|DEBUG|Specify|Step: Then money dispensed Result: Passed Duration: 5 milliseconds. 5 | 2014-03-25 11:30:05.0000|INFO|Specify|Scenario: Example Scenario Result: Failed Duration: 15 milliseconds. 6 | 2014-03-25 11:30:05.0000|DEBUG|Specify|Step: Given a account balance Result: Passed Duration: 5 milliseconds. 7 | 2014-03-25 11:30:05.0000|DEBUG|Specify|Step: When the account holder requests money Result: Passed Duration: 5 milliseconds. 8 | 2014-03-25 11:30:05.0000|DEBUG|Specify|Step: Then money dispensed Result: Failed Duration: 5 milliseconds. 9 | 2014-03-25 11:30:05.0000|ERROR|Specify|Boom 10 | With 11 | New lines 12 | -------------------------------------------------------------------------------- /src/tests/Specify.Tests/Configuration/Scanners/ConfigScannerFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NSubstitute; 4 | using NUnit.Framework; 5 | using Shouldly; 6 | using Specify.Autofac; 7 | using Specify.Configuration.Scanners; 8 | using Specify.Mocks; 9 | 10 | namespace Specify.Tests.Configuration.Scanners 11 | { 12 | public class ConfigScannerFactoryTests 13 | { 14 | private readonly Type[] _scannerTypes = new [] 15 | { 16 | typeof(SpecifyConfigScanner), 17 | typeof(SpecifyAutofacConfigScanner) 18 | }; 19 | 20 | [Test] 21 | public void specify_without_plugin_should_return_SpecifyConfigurationScanner() 22 | { 23 | var result = SelectScanner(_scannerTypes.Take(1).ToArray()); 24 | result.ShouldBeOfType(); 25 | } 26 | 27 | [Test] 28 | public void specify_with_plugin_should_return_plugin_ConfigurationScanner() 29 | { 30 | var result = SelectScanner(_scannerTypes); 31 | result.ShouldBeOfType(); 32 | } 33 | 34 | public IConfigScanner SelectScanner(Type[] types) 35 | { 36 | var fileSystem = Substitute.For(); 37 | fileSystem.GetAllTypesFromAppDomain().Returns(types); 38 | return ConfigScannerFactory.SelectScanner(fileSystem); 39 | } 40 | 41 | } 42 | } -------------------------------------------------------------------------------- /src/tests/Specify.Tests/Mocks/FakeItEasyMockFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using Specify.Mocks; 2 | 3 | namespace Specify.Tests.Mocks 4 | { 5 | public class FakeItEasyMockFactoryTests : MockFactoryTestsFor 6 | { 7 | protected override string AssemblyName => "FakeItEasy"; 8 | protected override string TypeName => "FakeItEasy.A"; 9 | protected override IMockFactory CreateSut(IFileSystem fileSystem) 10 | { 11 | return new FakeItEasyMockFactory(fileSystem); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/tests/Specify.Tests/Mocks/MockFactoryTestsFor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using NSubstitute; 5 | using NUnit.Framework; 6 | using Shouldly; 7 | using Specify.Mocks; 8 | 9 | namespace Specify.Tests.Mocks 10 | { 11 | [TestFixture] 12 | public abstract class MockFactoryTestsFor where T : IMockFactory, new() 13 | { 14 | protected abstract string AssemblyName { get; } 15 | protected abstract string TypeName { get; } 16 | 17 | protected abstract IMockFactory CreateSut(IFileSystem fileSystem); 18 | 19 | [Test] 20 | public void should_not_throw_FileNotFoundException_if_Mock_assembly_not_referenced() 21 | { 22 | var fileSystem = Substitute.For(); 23 | fileSystem.Load(Arg.Any()).Returns(x => { throw new FileNotFoundException(); }); 24 | Should.NotThrow(() => CreateSut(fileSystem)); 25 | } 26 | 27 | [Test] 28 | public void should_return_Name_of_mocking_framework() 29 | { 30 | var sut = CreateSut(Substitute.For()); 31 | sut.MockProviderName.ShouldBe(AssemblyName); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/tests/Specify.Tests/Mocks/MoqMockFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using Specify.Mocks; 2 | 3 | namespace Specify.Tests.Mocks 4 | { 5 | public class MoqMockFactoryTests : MockFactoryTestsFor 6 | { 7 | protected override string AssemblyName => "Moq"; 8 | protected override string TypeName => "Moq.Mock`1"; 9 | protected override IMockFactory CreateSut(IFileSystem fileSystem) 10 | { 11 | return new MoqMockFactory(fileSystem); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/tests/Specify.Tests/Mocks/NSubstituteMockFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using Specify.Mocks; 2 | 3 | namespace Specify.Tests.Mocks 4 | { 5 | public class NSubstituteMockFactoryTests : MockFactoryTestsFor 6 | { 7 | protected override string AssemblyName => "NSubstitute"; 8 | protected override string TypeName => "NSubstitute.Substitute"; 9 | protected override IMockFactory CreateSut(IFileSystem fileSystem) 10 | { 11 | return new NSubstituteMockFactory(fileSystem); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/tests/Specify.Tests/Properties/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("Specify.Tests")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] 14 | [assembly: InternalsVisibleTo("Specify.IntegrationTests")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("da325e84-dc76-4d03-a0bb-fcb6e83b1d7d")] 23 | 24 | [assembly: AssemblyVersion("2.4.0.0")] 25 | [assembly: AssemblyInformationalVersion("2.4.0-vnext.1+283.Branch.vnext.Sha.ba651f8dd1beb444d38d2be4f7e5de0057ad9f9a")] 26 | [assembly: AssemblyFileVersion("2.4.0.0")] 27 | -------------------------------------------------------------------------------- /src/tests/Specify.Tests/Specify.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | false 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | all 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/tests/Specify.Tests/SpecifyRootFolder/LoggingGatewayTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Shouldly; 3 | using Specify.Exceptions; 4 | using Specify.Logging; 5 | 6 | namespace Specify.Tests.SpecifyRootFolder 7 | { 8 | [TestFixture] 9 | public class LoggingGatewayTests 10 | { 11 | [Test] 12 | public void should_throw_LoggingException_for_invalid_log() 13 | { 14 | string sut = null; 15 | Should.Throw(() => sut.Log().Info("something")) 16 | .Message.ShouldBe("Failed to log for logger."); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/tests/Specify.Tests/Stories/UserStoryTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Shouldly; 3 | using Specify.Tests.Stubs; 4 | 5 | namespace Specify.Tests.Stories 6 | { 7 | public class UserStoryTests 8 | { 9 | private WithdrawCashUserStory SUT; 10 | 11 | [SetUp] 12 | public void SetUp() 13 | { 14 | SUT = new WithdrawCashUserStory(); 15 | } 16 | 17 | [Test] 18 | public void should_have_default_title_prefix_if_none_is_provided() 19 | { 20 | SUT.TitlePrefix.ShouldBe("Story: "); 21 | } 22 | 23 | [Test] 24 | public void should_return_overridden_properties() 25 | { 26 | SUT.AsA.ShouldBe("As an Account Holder"); 27 | } 28 | 29 | [Test] 30 | public void should_provide_omitted_clause_prefixes() 31 | { 32 | SUT.IWant.ShouldStartWith("I want"); 33 | } 34 | 35 | [Test] 36 | public void should_allow_omitting_clauses() 37 | { 38 | SUT.SoThat.ShouldBe(null); 39 | } 40 | 41 | [Test] 42 | public void should_be_able_to_set_title_and_title_prefix() 43 | { 44 | var sut = new TicTacToeUserStory(); 45 | sut.Title.ShouldBe("Tic Tac Toe Story"); 46 | sut.TitlePrefix.ShouldBe("User Story 1:"); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/tests/Specify.Tests/Stubs/TestableUnitScenario.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Specify.Tests.Stubs 4 | { 5 | abstract class TestableUnitScenario : ScenarioFor where TSut : class 6 | { 7 | private List _steps = new List(); 8 | 9 | public List Steps => _steps; 10 | } 11 | } -------------------------------------------------------------------------------- /src/tests/Specify.Tests/Stubs/TestableUserStoryScenario.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Specify.Tests.Stubs 4 | { 5 | abstract class TestableUserStoryScenario : ScenarioFor where TSut : class 6 | { 7 | private List _steps = new List(); 8 | 9 | public List Steps => _steps; 10 | } 11 | } -------------------------------------------------------------------------------- /src/tests/Specify.Tests/Stubs/UserStories.cs: -------------------------------------------------------------------------------- 1 | using Specify.Stories; 2 | 3 | namespace Specify.Tests.Stubs 4 | { 5 | class WithdrawCashUserStory : UserStory 6 | { 7 | public WithdrawCashUserStory() 8 | { 9 | AsA = "As an Account Holder"; 10 | IWant = "withdraw cash from an ATM"; 11 | } 12 | } 13 | 14 | class TicTacToeUserStory : UserStory 15 | { 16 | public TicTacToeUserStory() 17 | { 18 | Title = "Tic Tac Toe Story"; 19 | TitlePrefix = "User Story 1:"; 20 | AsA = "As a player"; 21 | IWant = "I want to have a tic tac toe game"; 22 | SoThat = "So that I can waste some time!"; 23 | } 24 | } 25 | 26 | class WithdrawCashValueStory : ValueStory 27 | { 28 | public WithdrawCashValueStory() 29 | { 30 | AsA = "As an Account Holder"; 31 | IWant = "withdraw cash from an ATM"; 32 | InOrderTo = null; 33 | } 34 | } 35 | 36 | class TicTacToeValueStory : ValueStory 37 | { 38 | public TicTacToeValueStory() 39 | { 40 | Title = "Tic Tac Toe Story"; 41 | TitlePrefix = "User Story 1:"; 42 | InOrderTo = "waste some time!"; 43 | AsA = "As a player"; 44 | IWant = "I want to have a tic tac toe game"; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/tests/Specify.Tests/TestsFor.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace Specify.Tests 4 | { 5 | [TestFixture] 6 | public abstract class TestsFor : Specify.TestsFor where TSut : class 7 | { 8 | [SetUp] 9 | public virtual void SetUp() 10 | { 11 | BaseSetup(); 12 | } 13 | 14 | [TearDown] 15 | public virtual void TearDown() 16 | { 17 | BaseTearDown(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/tests/Specify.Tests/nlog-sample.txt: -------------------------------------------------------------------------------- 1 | 2014-03-25 11:30:05.0000|INFO|Specify|Scenario: Example Scenario Result: Passed Duration: 15 milliseconds. 2 | 2014-03-25 11:30:05.0000|DEBUG|Specify|Step: Given a account balance Result: Passed Duration: 5 milliseconds. 3 | 2014-03-25 11:30:05.0000|DEBUG|Specify|Step: When the account holder requests money Result: Passed Duration: 5 milliseconds. 4 | 2014-03-25 11:30:05.0000|DEBUG|Specify|Step: Then money dispensed Result: Passed Duration: 5 milliseconds. 5 | 2014-03-25 11:30:05.0000|INFO|Specify|Scenario: Example Scenario Result: Failed Duration: 15 milliseconds. 6 | 2014-03-25 11:30:05.0000|DEBUG|Specify|Step: Given a account balance Result: Passed Duration: 5 milliseconds. 7 | 2014-03-25 11:30:05.0000|DEBUG|Specify|Step: When the account holder requests money Result: Passed Duration: 5 milliseconds. 8 | 2014-03-25 11:30:05.0000|DEBUG|Specify|Step: Then money dispensed Result: Failed Duration: 5 milliseconds. 9 | 2014-03-25 11:30:05.0000|ERROR|Specify|Boom 10 | With 11 | New lines 12 | --------------------------------------------------------------------------------