├── .dockerignore ├── .gitignore ├── Armut.sln ├── LICENSE ├── README.md ├── src ├── Armut.Api.Core │ ├── Armut.Api.Core.csproj │ ├── ArmutContext.cs │ ├── Components │ │ ├── Contract.cs │ │ └── ModelValidator.cs │ ├── Contracts │ │ ├── IEvent.cs │ │ ├── IEventService.cs │ │ ├── IInstaller.cs │ │ ├── IJobQuoteService.cs │ │ ├── IJobService.cs │ │ ├── IModelValidator.cs │ │ ├── IService.cs │ │ ├── IServicesService.cs │ │ └── IUserService.cs │ ├── Entities │ │ ├── EventEntity.cs │ │ ├── JobEntity.cs │ │ ├── JobQuoteEntity.cs │ │ ├── ProviderEntity.cs │ │ ├── ServiceEntity.cs │ │ └── UserEntity.cs │ ├── Exceptions │ │ ├── BaseException.cs │ │ ├── BaseExistsException.cs │ │ ├── BaseServiceException.cs │ │ ├── InvalidServiceOperationException.cs │ │ ├── ParameterRequiredException.cs │ │ └── UserExistsException.cs │ ├── Installers │ │ ├── AwsSdkInstaller.cs │ │ ├── EFCoreInstaller.cs │ │ ├── InstallerExtensions.cs │ │ └── ServiceInstaller.cs │ ├── MappingProfile.cs │ ├── Models │ │ ├── AddJobModel.cs │ │ ├── AddJobQuoteModel.cs │ │ ├── AddJobQuoteViewModel.cs │ │ ├── AddProviderModel.cs │ │ ├── AddUserModel.cs │ │ ├── AddUserViewModel.cs │ │ ├── Events │ │ │ ├── BaseEvent.cs │ │ │ ├── JobCreatedEvent.cs │ │ │ └── UserCreatedEvent.cs │ │ ├── JobModel.cs │ │ ├── JobQuoteModel.cs │ │ ├── ProviderModel.cs │ │ ├── ServiceModel.cs │ │ ├── UserModel.cs │ │ └── Validators │ │ │ ├── AddAddJobQuoteModelValidator.cs │ │ │ ├── AddJobModelValidator.cs │ │ │ ├── AddProviderModelValidator.cs │ │ │ └── AddUserModelValidator.cs │ └── Services │ │ ├── EventService.cs │ │ ├── JobQuoteService.cs │ │ ├── JobService.cs │ │ ├── ServicesService.cs │ │ └── UserService.cs ├── Armut.Api │ ├── Armut.Api.csproj │ ├── Constants.cs │ ├── Controllers │ │ ├── JobController.cs │ │ ├── JobQuoteController.cs │ │ ├── ServicesController.cs │ │ └── UserController.cs │ ├── Dockerfile │ ├── Options │ │ └── ArmutEventOptions.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── Armut.EventProcessor │ ├── Armut.EventProcessor.csproj │ ├── Function.cs │ ├── LambdaHostEnv.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Readme.md │ ├── appsettings.json │ ├── aws-lambda-tools-defaults.json │ ├── replace-localstack.sh │ └── serverless.yml └── Dockerfile └── tests ├── Armut.Api.Core.IntegrationTests ├── Armut.Api.Core.IntegrationTests.csproj ├── BaseTest.cs ├── CollectionDefinitions │ └── IntegrationTestCollection.cs ├── EventServiceTests.cs ├── UserServiceTests.cs └── appsettings.json ├── Armut.Api.Core.Tests ├── Armut.Api.Core.Tests.csproj └── UserServiceTests.cs ├── Armut.Api.FunctionalTests ├── Armut.Api.FunctionalTests.csproj ├── BaseScenario.cs ├── CollectionDefinitions │ └── ApiTestCollection.cs ├── Extensions │ └── HttpContentExtensions.cs ├── Routes │ ├── ApiVersion.cs │ └── UserRoots.cs ├── UserScenario.cs └── appsettings.json ├── Armut.EventProcessor.Tests ├── Armut.EventProcessor.Tests.csproj └── FunctionTest.cs └── Armut.Tests.Common ├── Armut.Tests.Common.csproj ├── Components └── TestValidatorFactory.cs ├── ContainerBuilder ├── TestcontainersBuilderWsl.cs └── WslMount.cs ├── Fixtures ├── DatabaseFixture.cs ├── FunctionDeployerFixture.cs ├── IntegrationTestFixture.cs ├── LocalStackFixture.cs └── TestServerFixture.cs ├── Helpers └── ImageHelper.cs └── Seeders ├── ISeeder.cs ├── Seeder.cs └── UserSeeder.cs /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /Armut.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30907.101 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0F634082-0CFC-4B33-988A-DB3DA3B4E77C}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D99FD148-CF21-4EF6-A6EE-3329D00206A9}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Armut.Api", "src\Armut.Api\Armut.Api.csproj", "{20C24F33-68AD-49A4-8038-87465A398448}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Armut.Api.Core", "src\Armut.Api.Core\Armut.Api.Core.csproj", "{43978A0C-1974-4AE0-BF45-0E3BF4688B8A}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Armut.Api.Core.Tests", "tests\Armut.Api.Core.Tests\Armut.Api.Core.Tests.csproj", "{1F5DC2A0-3DD9-496D-AEDF-4ACE165A234D}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Armut.Api.Core.IntegrationTests", "tests\Armut.Api.Core.IntegrationTests\Armut.Api.Core.IntegrationTests.csproj", "{44016949-9375-49F6-8AC1-0BA67AFBDB8D}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Armut.Api.FunctionalTests", "tests\Armut.Api.FunctionalTests\Armut.Api.FunctionalTests.csproj", "{3CEB0FE7-FFF1-4142-8974-328FA29E914C}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Armut.Tests.Common", "tests\Armut.Tests.Common\Armut.Tests.Common.csproj", "{903195EE-83F3-410B-8893-5585D21C1692}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Armut.EventProcessor", "src\Armut.EventProcessor\Armut.EventProcessor.csproj", "{6CC84EBB-ACB9-499E-9F34-505BA7BEF716}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Armut.EventProcessor.Tests", "tests\Armut.EventProcessor.Tests\Armut.EventProcessor.Tests.csproj", "{5C516121-A382-4169-8180-A1DF43CD6559}" 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {20C24F33-68AD-49A4-8038-87465A398448}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {20C24F33-68AD-49A4-8038-87465A398448}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {20C24F33-68AD-49A4-8038-87465A398448}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {20C24F33-68AD-49A4-8038-87465A398448}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {43978A0C-1974-4AE0-BF45-0E3BF4688B8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {43978A0C-1974-4AE0-BF45-0E3BF4688B8A}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {43978A0C-1974-4AE0-BF45-0E3BF4688B8A}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {43978A0C-1974-4AE0-BF45-0E3BF4688B8A}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {1F5DC2A0-3DD9-496D-AEDF-4ACE165A234D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {1F5DC2A0-3DD9-496D-AEDF-4ACE165A234D}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {1F5DC2A0-3DD9-496D-AEDF-4ACE165A234D}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {1F5DC2A0-3DD9-496D-AEDF-4ACE165A234D}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {44016949-9375-49F6-8AC1-0BA67AFBDB8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {44016949-9375-49F6-8AC1-0BA67AFBDB8D}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {44016949-9375-49F6-8AC1-0BA67AFBDB8D}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {44016949-9375-49F6-8AC1-0BA67AFBDB8D}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {3CEB0FE7-FFF1-4142-8974-328FA29E914C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {3CEB0FE7-FFF1-4142-8974-328FA29E914C}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {3CEB0FE7-FFF1-4142-8974-328FA29E914C}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {3CEB0FE7-FFF1-4142-8974-328FA29E914C}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {903195EE-83F3-410B-8893-5585D21C1692}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {903195EE-83F3-410B-8893-5585D21C1692}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {903195EE-83F3-410B-8893-5585D21C1692}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {903195EE-83F3-410B-8893-5585D21C1692}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {6CC84EBB-ACB9-499E-9F34-505BA7BEF716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {6CC84EBB-ACB9-499E-9F34-505BA7BEF716}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {6CC84EBB-ACB9-499E-9F34-505BA7BEF716}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {6CC84EBB-ACB9-499E-9F34-505BA7BEF716}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {5C516121-A382-4169-8180-A1DF43CD6559}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {5C516121-A382-4169-8180-A1DF43CD6559}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {5C516121-A382-4169-8180-A1DF43CD6559}.Release|Any CPU.ActiveCfg = Release|Any CPU 63 | {5C516121-A382-4169-8180-A1DF43CD6559}.Release|Any CPU.Build.0 = Release|Any CPU 64 | EndGlobalSection 65 | GlobalSection(SolutionProperties) = preSolution 66 | HideSolutionNode = FALSE 67 | EndGlobalSection 68 | GlobalSection(NestedProjects) = preSolution 69 | {20C24F33-68AD-49A4-8038-87465A398448} = {D99FD148-CF21-4EF6-A6EE-3329D00206A9} 70 | {43978A0C-1974-4AE0-BF45-0E3BF4688B8A} = {D99FD148-CF21-4EF6-A6EE-3329D00206A9} 71 | {1F5DC2A0-3DD9-496D-AEDF-4ACE165A234D} = {0F634082-0CFC-4B33-988A-DB3DA3B4E77C} 72 | {44016949-9375-49F6-8AC1-0BA67AFBDB8D} = {0F634082-0CFC-4B33-988A-DB3DA3B4E77C} 73 | {3CEB0FE7-FFF1-4142-8974-328FA29E914C} = {0F634082-0CFC-4B33-988A-DB3DA3B4E77C} 74 | {903195EE-83F3-410B-8893-5585D21C1692} = {0F634082-0CFC-4B33-988A-DB3DA3B4E77C} 75 | {6CC84EBB-ACB9-499E-9F34-505BA7BEF716} = {D99FD148-CF21-4EF6-A6EE-3329D00206A9} 76 | {5C516121-A382-4169-8180-A1DF43CD6559} = {0F634082-0CFC-4B33-988A-DB3DA3B4E77C} 77 | EndGlobalSection 78 | GlobalSection(ExtensibilityGlobals) = postSolution 79 | SolutionGuid = {6ED230F2-4475-4874-A63D-43EA8D7AA535} 80 | EndGlobalSection 81 | EndGlobal 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Armut 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dotnet-functional-tests-localstack-testcontainers 2 | Sample project for 2021 DotNetConf http://www.dotnetkonf.com/ 3 | 4 | # Getting Started 5 | 6 | Run this command before running tests 7 | 8 | ``` 9 | docker pull lambci/lambda:dotnetcore3.1 10 | ``` 11 | 12 | All other docker images will be downloaded by tests. Therefore, the first run may take a long time. 13 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Armut.Api.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/ArmutContext.cs: -------------------------------------------------------------------------------- 1 | using Armut.Api.Core.Entities; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace Armut.Api.Core 5 | { 6 | public sealed class ArmutContext : DbContext 7 | { 8 | public ArmutContext(DbContextOptions options) 9 | : base(options) 10 | { 11 | Database.AutoTransactionsEnabled = false; 12 | } 13 | 14 | protected override void OnModelCreating(ModelBuilder modelBuilder) 15 | { 16 | base.OnModelCreating(modelBuilder); 17 | 18 | 19 | 20 | } 21 | 22 | public DbSet Users { get; set; } 23 | 24 | public DbSet EventEntities { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Components/Contract.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace Armut.Api.Core.Components 5 | { 6 | public static class Contract 7 | { 8 | [AssertionMethod] 9 | public static void Requires([AssertionCondition(AssertionConditionType.IS_TRUE)]bool condition) where TException : Exception 10 | { 11 | if (condition) 12 | { 13 | return; 14 | } 15 | 16 | var exception = Activator.CreateInstance(); 17 | throw exception; 18 | } 19 | 20 | [AssertionMethod] 21 | public static void Requires([AssertionCondition(AssertionConditionType.IS_TRUE)]bool condition, string userMessage) where TException : Exception 22 | { 23 | if (condition) 24 | { 25 | return; 26 | } 27 | 28 | var exception = (TException)Activator.CreateInstance(typeof(TException), userMessage); 29 | throw exception; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Components/ModelValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Armut.Api.Core.Contracts; 4 | using FluentValidation; 5 | 6 | namespace Armut.Api.Core.Components 7 | { 8 | public class ModelValidator : IModelValidator 9 | { 10 | private readonly IValidatorFactory _validatorFactory; 11 | 12 | public ModelValidator(IValidatorFactory validatorFactory) 13 | { 14 | _validatorFactory = validatorFactory; 15 | } 16 | 17 | public Task ValidateAndThrowAsync(TModel model, CancellationToken token = default) 18 | { 19 | IValidator validator = _validatorFactory.GetValidator(); 20 | return validator.ValidateAndThrowAsync(model, cancellationToken: token); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Contracts/IEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Armut.Api.Core.Contracts 4 | { 5 | public interface IEvent 6 | { 7 | string Sender { get; set; } 8 | 9 | DateTime CreateDate { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Contracts/IEventService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Amazon.SimpleNotificationService.Model; 4 | using Armut.Api.Core.Models.Events; 5 | 6 | namespace Armut.Api.Core.Contracts 7 | { 8 | public interface IEventService : IService 9 | { 10 | Task SaveUserCreatedEvent(UserCreatedEvent userCreatedEvent, CancellationToken token = default); 11 | Task SaveJobCreatedEvent(JobCreatedEvent jobCreatedEvent, CancellationToken token = default); 12 | Task PublishEvent(TEvent @event, string topicArn, CancellationToken token = default) where TEvent: IEvent; 13 | } 14 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Contracts/IInstaller.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | 5 | namespace Armut.Api.Core.Contracts 6 | { 7 | public interface IInstaller 8 | { 9 | void Install(IServiceCollection services, IConfiguration configuration, IHostEnvironment hostEnvironment); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Contracts/IJobQuoteService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Armut.Api.Core.Models; 5 | 6 | namespace Armut.Api.Core.Contracts 7 | { 8 | public interface IJobQuoteService : IService 9 | { 10 | Task AddJobQuote(AddJobQuoteModel addJobQuoteModel, CancellationToken token = default); 11 | 12 | Task> GetJobQuotes(int jobId, CancellationToken token = default); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Contracts/IJobService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Armut.Api.Core.Models; 4 | 5 | namespace Armut.Api.Core.Contracts 6 | { 7 | public interface IJobService : IService 8 | { 9 | Task AddJob(AddJobModel addJobModel, CancellationToken token = default); 10 | 11 | Task GetJobById(int jobId, CancellationToken token = default); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Contracts/IModelValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Armut.Api.Core.Contracts 5 | { 6 | public interface IModelValidator 7 | { 8 | Task ValidateAndThrowAsync(TModel model, CancellationToken token = default); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Contracts/IService.cs: -------------------------------------------------------------------------------- 1 | namespace Armut.Api.Core.Contracts 2 | { 3 | public interface IService 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Contracts/IServicesService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Armut.Api.Core.Models; 5 | 6 | namespace Armut.Api.Core.Contracts 7 | { 8 | public interface IServicesService : IService 9 | { 10 | Task> GetServices(CancellationToken token = default); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Contracts/IUserService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Armut.Api.Core.Models; 4 | 5 | namespace Armut.Api.Core.Contracts 6 | { 7 | public interface IUserService : IService 8 | { 9 | Task AddUser(AddUserModel addUserModel, CancellationToken token = default); 10 | 11 | Task GetUserById(int userId, CancellationToken cancellationToken = default); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Entities/EventEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace Armut.Api.Core.Entities 6 | { 7 | public class EventEntity 8 | { 9 | [Key] 10 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 11 | public int Id { get; set; } 12 | 13 | public string Sender { get; set; } 14 | 15 | public int EventRelationId { get; set; } 16 | 17 | public string EventType { get; set; } 18 | 19 | public string Message { get; set; } 20 | 21 | public DateTime CreateDate { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Entities/JobEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace Armut.Api.Core.Entities 6 | { 7 | public class JobEntity 8 | { 9 | [Key] 10 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 11 | public int Id { get; set; } 12 | 13 | public int UserId { get; set; } 14 | 15 | public int ServiceId { get; set; } 16 | 17 | public string Description { get; set; } 18 | 19 | public DateTime JobStartDateTime { get; set; } 20 | 21 | public DateTime CreateDate { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Entities/JobQuoteEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace Armut.Api.Core.Entities 6 | { 7 | public class JobQuoteEntity 8 | { 9 | [Key] 10 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 11 | public int Id { get; set; } 12 | 13 | public int JobId { get; set; } 14 | 15 | public int UserId { get; set; } 16 | 17 | public int ProviderId { get; set; } 18 | 19 | public double QuotePrice { get; set; } 20 | 21 | public DateTime CreateDate { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Entities/ProviderEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace Armut.Api.Core.Entities 6 | { 7 | public class ProviderEntity 8 | { 9 | [Key] 10 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 11 | public int Id { get; set; } 12 | 13 | public string FirstName { get; set; } 14 | 15 | public string LastName { get; set; } 16 | 17 | public string Email { get; set; } 18 | 19 | public int ServiceId { get; set; } 20 | 21 | public string ProfilePicture { get; set; } 22 | 23 | public DateTime CreateDate { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Entities/ServiceEntity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace Armut.Api.Core.Entities 5 | { 6 | public class ServiceEntity 7 | { 8 | [Key] 9 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 10 | public int Id { get; set; } 11 | 12 | public string ServiceName { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Entities/UserEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace Armut.Api.Core.Entities 6 | { 7 | public class UserEntity 8 | { 9 | [Key] 10 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 11 | public int Id { get; set; } 12 | 13 | public string FirstName { get; set; } 14 | 15 | public string LastName { get; set; } 16 | 17 | public string Email { get; set; } 18 | 19 | public string ProfilePictureUrl { get; set; } 20 | 21 | public DateTime CreateDate { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Exceptions/BaseException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace Armut.Api.Core.Exceptions 5 | { 6 | [Serializable] 7 | public abstract class BaseException : Exception 8 | { 9 | protected BaseException() 10 | { 11 | } 12 | 13 | protected BaseException(string message) : base(message) 14 | { 15 | } 16 | 17 | protected BaseException(string message, Exception innerException) : base(message, innerException) 18 | { 19 | } 20 | 21 | protected BaseException(SerializationInfo info, StreamingContext context) : base(info, context) 22 | { 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Exceptions/BaseExistsException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Armut.Api.Core.Exceptions 4 | { 5 | [Serializable] 6 | public abstract class BaseExistsException : InvalidServiceOperationException 7 | { 8 | public BaseExistsException(string message) : base(message) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Exceptions/BaseServiceException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using System.Text; 5 | 6 | namespace Armut.Api.Core.Exceptions 7 | { 8 | [Serializable] 9 | public abstract class BaseServiceException : BaseException 10 | { 11 | protected BaseServiceException() 12 | { 13 | } 14 | 15 | protected BaseServiceException(string message) : base(message) 16 | { 17 | } 18 | 19 | protected BaseServiceException(string message, Exception innerException) : base(message, innerException) 20 | { 21 | } 22 | 23 | protected BaseServiceException(SerializationInfo info, StreamingContext context) : base(info, context) 24 | { 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Exceptions/InvalidServiceOperationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Armut.Api.Core.Exceptions 4 | { 5 | [Serializable] 6 | public abstract class InvalidServiceOperationException : BaseServiceException 7 | { 8 | protected InvalidServiceOperationException(string message) : base(message) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Exceptions/ParameterRequiredException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Armut.Api.Core.Exceptions 6 | { 7 | public class ParameterRequiredException : BaseServiceException 8 | { 9 | protected ParameterRequiredException(string fieldName) 10 | : base(fieldName) 11 | { 12 | Reason = "required"; 13 | } 14 | 15 | public string Reason { get; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Exceptions/UserExistsException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Armut.Api.Core.Exceptions 4 | { 5 | [Serializable] 6 | public class UserExistsException : BaseExistsException 7 | { 8 | public UserExistsException(string message) : base(message) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Installers/AwsSdkInstaller.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Amazon; 5 | using Amazon.Extensions.NETCore.Setup; 6 | using Amazon.Lambda; 7 | using Amazon.Runtime; 8 | using Amazon.S3; 9 | using Amazon.S3.Transfer; 10 | using Amazon.SimpleNotificationService; 11 | using Armut.Api.Core.Contracts; 12 | using LocalStack.Client.Extensions; 13 | using Microsoft.Extensions.Configuration; 14 | using Microsoft.Extensions.DependencyInjection; 15 | using Microsoft.Extensions.Hosting; 16 | 17 | namespace Armut.Api.Core.Installers 18 | { 19 | public class AwsSdkInstaller : IInstaller 20 | { 21 | public void Install(IServiceCollection services, IConfiguration configuration, IHostEnvironment webHostEnvironment) 22 | { 23 | services.AddDefaultAWSOptions(GetAwsOptions(configuration)); 24 | services.AddLocalStack(configuration); 25 | services.AddAwsService(); 26 | services.AddAwsService(); 27 | services.AddAwsService(); 28 | services.AddSingleton(provider => 29 | { 30 | var s3Client = provider.GetService(); 31 | return new TransferUtility(s3Client); 32 | }); 33 | } 34 | 35 | private AWSOptions GetAwsOptions(IConfiguration configuration) 36 | { 37 | string awsAccessKey = configuration.GetSection("AWSAccessKey").Value; 38 | string awsSecretKey = configuration.GetSection("AWSSecretKey").Value; 39 | 40 | var awsOptions = new AWSOptions 41 | { 42 | Region = RegionEndpoint.EUCentral1, 43 | Credentials = new BasicAWSCredentials(awsAccessKey, awsSecretKey) 44 | }; 45 | 46 | return awsOptions; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Installers/EFCoreInstaller.cs: -------------------------------------------------------------------------------- 1 | using Armut.Api.Core.Contracts; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace Armut.Api.Core.Installers 8 | { 9 | public class EFCoreInstaller : IInstaller 10 | { 11 | public void Install(IServiceCollection services, IConfiguration configuration, IHostEnvironment hostEnvironment) 12 | { 13 | string connectionString = configuration.GetConnectionString("Database"); 14 | 15 | services.AddDbContextPool(options => 16 | { 17 | options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); 18 | options.UseNpgsql(connectionString); 19 | }); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Installers/InstallerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Armut.Api.Core.Contracts; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Hosting; 7 | 8 | namespace Armut.Api.Core.Installers 9 | { 10 | public static class InstallerExtensions 11 | { 12 | public static void InstallServices(this IServiceCollection services, IConfiguration configuration, IHostEnvironment hostEnvironment) 13 | { 14 | var installers = typeof(InstallerExtensions).Assembly.ExportedTypes 15 | .Where(m => typeof(IInstaller).IsAssignableFrom(m) && !m.IsInterface && !m.IsAbstract) 16 | .Select(Activator.CreateInstance) 17 | .Cast() 18 | .ToList(); 19 | 20 | installers.ForEach(m => m.Install(services, configuration, hostEnvironment)); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Installers/ServiceInstaller.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Armut.Api.Core.Components; 6 | using Armut.Api.Core.Contracts; 7 | using Armut.Api.Core.Services; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Hosting; 11 | 12 | namespace Armut.Api.Core.Installers 13 | { 14 | public class ServiceInstaller : IInstaller 15 | { 16 | public void Install(IServiceCollection services, IConfiguration configuration, IHostEnvironment hostEnvironment) 17 | { 18 | Assembly assembly = typeof(UserService).Assembly; 19 | 20 | foreach (Type type in assembly.GetTypes()) 21 | { 22 | bool isComponent = type.GetInterfaces().Contains(typeof(IService)); 23 | 24 | if (!isComponent) 25 | { 26 | continue; 27 | } 28 | 29 | Type componentInterfaceType = type.GetInterfaces().FirstOrDefault(interfaceType => 30 | interfaceType.GetInterfaces().Contains(typeof(IService)) && 31 | interfaceType.GetInterfaces().Length == 1); 32 | 33 | if (componentInterfaceType != null) 34 | { 35 | services.AddTransient(componentInterfaceType, type); 36 | } 37 | } 38 | 39 | services.AddTransient(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using Armut.Api.Core.Entities; 2 | using Armut.Api.Core.Models; 3 | using AutoMapper; 4 | 5 | namespace Armut.Api.Core 6 | { 7 | public class MappingProfile : Profile 8 | { 9 | public MappingProfile() 10 | { 11 | CreateMap().ReverseMap(); 12 | CreateMap().ReverseMap(); 13 | CreateMap().ReverseMap(); 14 | CreateMap().ReverseMap(); 15 | 16 | CreateMap(); 17 | CreateMap(); 18 | 19 | CreateMap().ReverseMap(); 20 | CreateMap().ReverseMap(); 21 | CreateMap().ReverseMap(); 22 | CreateMap().ReverseMap(); 23 | CreateMap().ReverseMap(); 24 | 25 | CreateMap(); 26 | CreateMap(); 27 | CreateMap(); 28 | CreateMap(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/AddJobModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Armut.Api.Core.Models 4 | { 5 | public class AddJobModel 6 | { 7 | public int UserId { get; set; } 8 | 9 | public int ServiceId { get; set; } 10 | 11 | public string Description { get; set; } 12 | 13 | public DateTime JobStartDateTime { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/AddJobQuoteModel.cs: -------------------------------------------------------------------------------- 1 | namespace Armut.Api.Core.Models 2 | { 3 | public class AddJobQuoteModel 4 | { 5 | public int UserId { get; set; } 6 | 7 | public int JobId { get; set; } 8 | 9 | public int ProviderId { get; set; } 10 | 11 | public double QuotePrice { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/AddJobQuoteViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Armut.Api.Core.Models 2 | { 3 | public class AddJobQuoteViewModel 4 | { 5 | public int UserId { get; set; } 6 | 7 | public int ProviderId { get; set; } 8 | 9 | public double QuotePrice { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/AddProviderModel.cs: -------------------------------------------------------------------------------- 1 | namespace Armut.Api.Core.Models 2 | { 3 | public class AddProviderModel 4 | { 5 | public string FirstName { get; set; } 6 | 7 | public string LastName { get; set; } 8 | 9 | public string Email { get; set; } 10 | 11 | public int ServiceId { get; set; } 12 | 13 | public string ProfilePicture { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/AddUserModel.cs: -------------------------------------------------------------------------------- 1 | namespace Armut.Api.Core.Models 2 | { 3 | public class AddUserModel 4 | { 5 | public string FirstName { get; set; } 6 | 7 | public string LastName { get; set; } 8 | 9 | public string Email { get; set; } 10 | 11 | public string ProfilePictureUrl { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/AddUserViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Armut.Api.Core.Models 2 | { 3 | public class AddUserViewModel 4 | { 5 | public string FirstName { get; set; } 6 | 7 | public string LastName { get; set; } 8 | 9 | public string Email { get; set; } 10 | 11 | public string ProfilePictureBase64 { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/Events/BaseEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Armut.Api.Core.Contracts; 3 | 4 | namespace Armut.Api.Core.Models.Events 5 | { 6 | public abstract class BaseEvent : IEvent where TModel : class, new() 7 | { 8 | public string Sender { get; set; } 9 | 10 | public TModel Payload { get; set; } 11 | 12 | public DateTime CreateDate { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/Events/JobCreatedEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Armut.Api.Core.Models.Events 2 | { 3 | public class JobCreatedEvent : BaseEvent 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/Events/UserCreatedEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Armut.Api.Core.Models.Events 2 | { 3 | public class UserCreatedEvent : BaseEvent 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/JobModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Armut.Api.Core.Models 4 | { 5 | public class JobModel 6 | { 7 | public int Id { get; set; } 8 | 9 | public int UserId { get; set; } 10 | 11 | public int ServiceId { get; set; } 12 | 13 | public string Description { get; set; } 14 | 15 | public DateTime JobStartDateTime { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/JobQuoteModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Armut.Api.Core.Models 4 | { 5 | public class JobQuoteModel 6 | { 7 | public int Id { get; set; } 8 | 9 | public int JobId { get; set; } 10 | 11 | public int UserId { get; set; } 12 | 13 | public int ProviderId { get; set; } 14 | 15 | public double QuotePrice { get; set; } 16 | 17 | public DateTime CreateDate { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/ProviderModel.cs: -------------------------------------------------------------------------------- 1 | namespace Armut.Api.Core.Models 2 | { 3 | public class ProviderModel 4 | { 5 | public int Id { get; set; } 6 | 7 | public string FirstName { get; set; } 8 | 9 | public string LastName { get; set; } 10 | 11 | public string Email { get; set; } 12 | 13 | public int ServiceId { get; set; } 14 | 15 | public string ProfilePicture { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/ServiceModel.cs: -------------------------------------------------------------------------------- 1 | namespace Armut.Api.Core.Models 2 | { 3 | public class ServiceModel 4 | { 5 | public int Id { get; set; } 6 | 7 | public string ServiceName { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/UserModel.cs: -------------------------------------------------------------------------------- 1 | namespace Armut.Api.Core.Models 2 | { 3 | public class UserModel 4 | { 5 | public int Id { get; set; } 6 | 7 | public string FirstName { get; set; } 8 | 9 | public string LastName { get; set; } 10 | 11 | public string Email { get; set; } 12 | 13 | public string ProfilePictureUrl { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/Validators/AddAddJobQuoteModelValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace Armut.Api.Core.Models.Validators 4 | { 5 | public class AddAddJobQuoteModelValidator : AbstractValidator 6 | { 7 | public AddAddJobQuoteModelValidator() 8 | { 9 | RuleFor(model => model.UserId).NotNull().NotEmpty().GreaterThan(0).WithMessage("UserId is required"); 10 | RuleFor(model => model.JobId).NotNull().NotEmpty().GreaterThan(0).WithMessage("JobId is required"); 11 | RuleFor(model => model.ProviderId).NotNull().NotEmpty().GreaterThan(0).WithMessage("ProviderId is required"); 12 | RuleFor(model => model.QuotePrice).NotNull().NotEmpty().GreaterThan(0).WithMessage("QuotePrice is required"); 13 | } 14 | } 15 | 16 | public class AddJobQuoteViewModelValidator : AbstractValidator 17 | { 18 | public AddJobQuoteViewModelValidator() 19 | { 20 | RuleFor(model => model.UserId).NotNull().NotEmpty().GreaterThan(0).WithMessage("UserId is required"); 21 | RuleFor(model => model.ProviderId).NotNull().NotEmpty().GreaterThan(0).WithMessage("ProviderId is required"); 22 | RuleFor(model => model.QuotePrice).NotNull().NotEmpty().GreaterThan(0).WithMessage("QuotePrice is required"); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/Validators/AddJobModelValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace Armut.Api.Core.Models.Validators 4 | { 5 | public class AddJobModelValidator : AbstractValidator 6 | { 7 | public AddJobModelValidator() 8 | { 9 | RuleFor(model => model.UserId).NotNull().NotEmpty().GreaterThan(0).WithMessage("UserId is required"); 10 | RuleFor(model => model.ServiceId).NotNull().NotEmpty().GreaterThan(0).WithMessage("ServiceId is required"); 11 | RuleFor(model => model.Description).NotNull().NotEmpty().WithMessage("Description is required"); 12 | RuleFor(model => model.JobStartDateTime).NotNull().NotEmpty().WithMessage("JobStartDateTime is required"); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/Validators/AddProviderModelValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace Armut.Api.Core.Models.Validators 4 | { 5 | public class AddProviderModelValidator : AbstractValidator 6 | { 7 | public AddProviderModelValidator() 8 | { 9 | RuleFor(model => model.Email).NotNull().NotEmpty().EmailAddress().WithMessage("Invalid mail address"); 10 | RuleFor(model => model.FirstName).NotNull().NotEmpty().WithMessage("FirstName is required"); 11 | RuleFor(model => model.LastName).NotNull().NotEmpty().WithMessage("LastName is required"); 12 | RuleFor(model => model.ServiceId).NotNull().NotEmpty().GreaterThan(0).WithMessage("ServiceId is required"); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Models/Validators/AddUserModelValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace Armut.Api.Core.Models.Validators 4 | { 5 | public class AddUserModelValidator : AbstractValidator 6 | { 7 | public AddUserModelValidator() 8 | { 9 | RuleFor(model => model.Email).NotNull().NotEmpty().EmailAddress().WithMessage("Invalid mail address"); 10 | RuleFor(model => model.FirstName).NotNull().NotEmpty().WithMessage("FirstName is required"); 11 | RuleFor(model => model.LastName).NotNull().NotEmpty().WithMessage("LastName is required"); 12 | } 13 | } 14 | 15 | public class AddUserViewModelValidator : AbstractValidator 16 | { 17 | public AddUserViewModelValidator() 18 | { 19 | RuleFor(model => model.Email).NotNull().NotEmpty().EmailAddress().WithMessage("Invalid mail address"); 20 | RuleFor(model => model.FirstName).NotNull().NotEmpty().WithMessage("FirstName is required"); 21 | RuleFor(model => model.LastName).NotNull().NotEmpty().WithMessage("LastName is required"); 22 | RuleFor(model => model.ProfilePictureBase64).NotNull().NotEmpty().WithMessage("ProfilePictureBase64 is required"); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Services/EventService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Amazon.SimpleNotificationService; 6 | using Amazon.SimpleNotificationService.Model; 7 | using Armut.Api.Core.Contracts; 8 | using Armut.Api.Core.Entities; 9 | using Armut.Api.Core.Models.Events; 10 | 11 | namespace Armut.Api.Core.Services 12 | { 13 | public class EventService : IEventService 14 | { 15 | private readonly ArmutContext _armutContext; 16 | private readonly IAmazonSimpleNotificationService _amazonSimpleNotificationService; 17 | 18 | public EventService(ArmutContext armutContext, IAmazonSimpleNotificationService amazonSimpleNotificationService) 19 | { 20 | _armutContext = armutContext; 21 | _amazonSimpleNotificationService = amazonSimpleNotificationService; 22 | } 23 | 24 | public async Task SaveUserCreatedEvent(UserCreatedEvent userCreatedEvent, CancellationToken token = default) 25 | { 26 | var eventEntity = new EventEntity() 27 | { 28 | Sender = userCreatedEvent.Sender, 29 | EventType = nameof(UserCreatedEvent), 30 | EventRelationId = userCreatedEvent.Payload.Id, 31 | Message = JsonSerializer.Serialize(userCreatedEvent.Payload), 32 | CreateDate = DateTime.Now 33 | }; 34 | 35 | await _armutContext.AddAsync(eventEntity, token); 36 | await _armutContext.SaveChangesAsync(token); 37 | } 38 | 39 | public async Task SaveJobCreatedEvent(JobCreatedEvent jobCreatedEvent, CancellationToken token = default) 40 | { 41 | var eventEntity = new EventEntity() 42 | { 43 | Sender = jobCreatedEvent.Sender, 44 | EventType = nameof(JobCreatedEvent), 45 | EventRelationId = jobCreatedEvent.Payload.Id, 46 | Message = JsonSerializer.Serialize(jobCreatedEvent.Payload), 47 | CreateDate = DateTime.Now 48 | }; 49 | 50 | await _armutContext.AddAsync(eventEntity, token); 51 | await _armutContext.SaveChangesAsync(token); 52 | } 53 | 54 | public Task PublishEvent(TEvent @event, string topicArn, CancellationToken token = default) where TEvent: IEvent 55 | { 56 | var request = new PublishRequest 57 | { 58 | Message = JsonSerializer.Serialize(@event), 59 | TopicArn = topicArn, 60 | }; 61 | 62 | return _amazonSimpleNotificationService.PublishAsync(request, token); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Armut.Api.Core/Services/JobQuoteService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Armut.Api.Core.Contracts; 5 | using Armut.Api.Core.Models; 6 | 7 | namespace Armut.Api.Core.Services 8 | { 9 | public class JobQuoteService : IJobQuoteService 10 | { 11 | private readonly ArmutContext _armutContext; 12 | 13 | public JobQuoteService(ArmutContext armutContext) 14 | { 15 | _armutContext = armutContext; 16 | } 17 | 18 | public Task AddJobQuote(AddJobQuoteModel addJobQuoteModel, CancellationToken token = default) 19 | { 20 | throw new System.NotImplementedException(); 21 | } 22 | 23 | public Task> GetJobQuotes(int jobId, CancellationToken token = default) 24 | { 25 | throw new System.NotImplementedException(); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Services/JobService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Armut.Api.Core.Contracts; 4 | using Armut.Api.Core.Models; 5 | 6 | namespace Armut.Api.Core.Services 7 | { 8 | public class JobService : IJobService 9 | { 10 | private readonly ArmutContext _armutContext; 11 | 12 | public JobService(ArmutContext armutContext) 13 | { 14 | _armutContext = armutContext; 15 | } 16 | 17 | public Task AddJob(AddJobModel addJobModel, CancellationToken token = default) 18 | { 19 | throw new System.NotImplementedException(); 20 | } 21 | 22 | public Task GetJobById(int jobId, CancellationToken token = default) 23 | { 24 | throw new System.NotImplementedException(); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Services/ServicesService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Armut.Api.Core.Contracts; 5 | using Armut.Api.Core.Models; 6 | 7 | namespace Armut.Api.Core.Services 8 | { 9 | public class ServicesService : IServicesService 10 | { 11 | private readonly ArmutContext _armutContext; 12 | 13 | public ServicesService(ArmutContext armutContext) 14 | { 15 | _armutContext = armutContext; 16 | } 17 | 18 | public Task> GetServices(CancellationToken token = default) 19 | { 20 | throw new System.NotImplementedException(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Armut.Api.Core/Services/UserService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Armut.Api.Core.Components; 5 | using Armut.Api.Core.Contracts; 6 | using Armut.Api.Core.Entities; 7 | using Armut.Api.Core.Exceptions; 8 | using Armut.Api.Core.Models; 9 | using AutoMapper; 10 | using Microsoft.EntityFrameworkCore; 11 | 12 | namespace Armut.Api.Core.Services 13 | { 14 | public class UserService : IUserService 15 | { 16 | private readonly ArmutContext _armutContext; 17 | private readonly IModelValidator _modelValidator; 18 | private readonly IMapper _mapper; 19 | 20 | public UserService(ArmutContext armutContext, IModelValidator modelValidator, IMapper mapper) 21 | { 22 | _armutContext = armutContext; 23 | _modelValidator = modelValidator; 24 | _mapper = mapper; 25 | } 26 | 27 | public async Task AddUser(AddUserModel addUserModel, CancellationToken token = default) 28 | { 29 | await _modelValidator.ValidateAndThrowAsync(addUserModel, token); 30 | 31 | bool userExists = await _armutContext.Users.AnyAsync(entity => entity.Email == addUserModel.Email, token); 32 | 33 | if (userExists) 34 | { 35 | throw new UserExistsException($"{addUserModel.Email}"); 36 | } 37 | 38 | var userEntity = _mapper.Map(addUserModel); 39 | userEntity.CreateDate = DateTime.Now; 40 | 41 | await _armutContext.AddAsync(userEntity, token); 42 | await _armutContext.SaveChangesAsync(token); 43 | 44 | var userModel = _mapper.Map(userEntity); 45 | 46 | return userModel; 47 | } 48 | 49 | public Task GetUserById(int userId, CancellationToken cancellationToken = default) 50 | { 51 | Contract.Requires(userId > 0, nameof(userId)); 52 | 53 | return _armutContext.Users 54 | .FirstOrDefaultAsync(entity => entity.Id == userId, cancellationToken) 55 | .ContinueWith(task => _mapper.Map(task.Result), TaskContinuationOptions.OnlyOnRanToCompletion); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Armut.Api/Armut.Api.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 932d2323-9cdb-40de-a4d8-d767e8003adf 6 | Linux 7 | ..\.. 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Armut.Api/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Armut.Api 7 | { 8 | public static class Constants 9 | { 10 | public const string ProfilePictureBucket = "profile-pictures"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Armut.Api/Controllers/JobController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Armut.Api.Core.Contracts; 4 | using Armut.Api.Core.Models; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.Mvc; 7 | 8 | namespace Armut.Api.Controllers 9 | { 10 | [Route("api/[controller]")] 11 | [ApiController] 12 | public class JobController : ControllerBase 13 | { 14 | private readonly IJobService _jobService; 15 | 16 | public JobController(IJobService jobService) 17 | { 18 | _jobService = jobService; 19 | } 20 | 21 | 22 | [HttpPost] 23 | [ProducesResponseType(typeof(JobModel), StatusCodes.Status201Created)] 24 | public async Task AddJob(AddJobModel addJobModel, CancellationToken token) 25 | { 26 | JobModel jobModel = await _jobService.AddJob(addJobModel, token); 27 | 28 | return CreatedAtAction("GetJob", jobModel); 29 | } 30 | 31 | [HttpGet] 32 | [Route("{job_id}")] 33 | [ProducesResponseType(typeof(JobModel), StatusCodes.Status200OK)] 34 | public async Task GetJob([FromRoute(Name = "job_id")]int jobId, CancellationToken cancellationToken) 35 | { 36 | JobModel userModel = await _jobService.GetJobById(jobId, cancellationToken); 37 | 38 | return Ok(userModel); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Armut.Api/Controllers/JobQuoteController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Armut.Api.Core.Contracts; 5 | using Armut.Api.Core.Models; 6 | using AutoMapper; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.Mvc; 9 | 10 | namespace Armut.Api.Controllers 11 | { 12 | [Route("api/[controller]")] 13 | [ApiController] 14 | public class JobQuoteController : ControllerBase 15 | { 16 | private readonly IJobQuoteService _jobQuoteService; 17 | private readonly IMapper _mapper; 18 | 19 | public JobQuoteController(IJobQuoteService jobQuoteService, IMapper mapper) 20 | { 21 | _jobQuoteService = jobQuoteService; 22 | _mapper = mapper; 23 | } 24 | 25 | [HttpPost] 26 | [Route("{job_id}")] 27 | [ProducesResponseType(typeof(JobQuoteModel), StatusCodes.Status201Created)] 28 | public async Task AddJobQuote( 29 | [FromRoute(Name = "job_id")] int jobId, 30 | AddJobQuoteViewModel addJobQuoteViewModel, 31 | CancellationToken token) 32 | { 33 | if (jobId <= 0) 34 | { 35 | return BadRequest($"{nameof(jobId)} is required"); 36 | } 37 | 38 | var addJobQuoteModel = _mapper.Map(addJobQuoteViewModel); 39 | addJobQuoteModel.JobId = jobId; 40 | 41 | JobQuoteModel jobQuoteModel = await _jobQuoteService.AddJobQuote(addJobQuoteModel, token); 42 | 43 | return CreatedAtAction("GetJobQuotes", jobQuoteModel); 44 | } 45 | 46 | [HttpGet] 47 | [Route("{job_id}")] 48 | [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] 49 | public async Task GetJobQuotes([FromRoute(Name = "job_id")]int jobId, CancellationToken cancellationToken) 50 | { 51 | IEnumerable jobQuoteModels = await _jobQuoteService.GetJobQuotes(jobId, cancellationToken); 52 | 53 | return Ok(jobQuoteModels); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Armut.Api/Controllers/ServicesController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Armut.Api.Core.Contracts; 6 | using Armut.Api.Core.Models; 7 | using Microsoft.AspNetCore.Http; 8 | 9 | namespace Armut.Api.Controllers 10 | { 11 | [Route("api/[controller]")] 12 | [ApiController] 13 | public class ServicesController : ControllerBase 14 | { 15 | private readonly IServicesService _servicesService; 16 | 17 | public ServicesController(IServicesService servicesService) 18 | { 19 | _servicesService = servicesService; 20 | } 21 | 22 | [HttpGet] 23 | [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] 24 | public async Task GetServices(CancellationToken token) 25 | { 26 | IEnumerable serviceModels = await _servicesService.GetServices(token); 27 | 28 | return Ok(serviceModels); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Armut.Api/Controllers/UserController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Amazon.S3; 7 | using Amazon.S3.Model; 8 | using Amazon.SimpleNotificationService.Model; 9 | using Microsoft.AspNetCore.Mvc; 10 | using Armut.Api.Core.Contracts; 11 | using Armut.Api.Core.Models; 12 | using Armut.Api.Core.Models.Events; 13 | using Armut.Api.Options; 14 | using AutoMapper; 15 | using Microsoft.AspNetCore.Http; 16 | using Microsoft.Extensions.Options; 17 | 18 | namespace Armut.Api.Controllers 19 | { 20 | [Route("api/[controller]")] 21 | [ApiController] 22 | public class UserController : ControllerBase 23 | { 24 | private readonly IUserService _userService; 25 | private readonly IEventService _eventService; 26 | private readonly ArmutEventOptions _armutEventOptions; 27 | private readonly IAmazonS3 _amazonS3; 28 | private readonly IMapper _mapper; 29 | 30 | public UserController(IUserService userService, IEventService eventService, IOptions eventOptions, IAmazonS3 amazonS3, IMapper mapper) 31 | { 32 | _userService = userService; 33 | _eventService = eventService; 34 | _armutEventOptions = new ArmutEventOptions() {SnsTopic = "arn:aws:sns:eu-central-1:000000000000:dispatch"}; 35 | _amazonS3 = amazonS3; 36 | _mapper = mapper; 37 | } 38 | 39 | 40 | [HttpPost] 41 | [ProducesResponseType(typeof(UserModel), StatusCodes.Status201Created)] 42 | [ProducesResponseType(StatusCodes.Status400BadRequest)] 43 | public async Task AddUser(AddUserViewModel addUserViewModel, CancellationToken cancellationToken) 44 | { 45 | byte[] bytes = Convert.FromBase64String(addUserViewModel.ProfilePictureBase64); 46 | 47 | var imgNameWithoutExtension = Guid.NewGuid().ToString(); 48 | var imgName = $"{imgNameWithoutExtension}.jpeg"; 49 | 50 | await using (var ms = new MemoryStream(bytes)) 51 | { 52 | var request = new PutObjectRequest() 53 | { 54 | BucketName = Constants.ProfilePictureBucket, 55 | Key = imgName, 56 | InputStream = ms 57 | }; 58 | 59 | PutObjectResponse amazonS3Response = await _amazonS3.PutObjectAsync(request, cancellationToken); 60 | 61 | if (amazonS3Response.HttpStatusCode != HttpStatusCode.OK) 62 | { 63 | return BadRequest(); 64 | } 65 | } 66 | 67 | 68 | var addUserModel = _mapper.Map(addUserViewModel); 69 | addUserModel.ProfilePictureUrl = imgName; 70 | 71 | UserModel userModel = await _userService.AddUser(addUserModel, cancellationToken); 72 | 73 | PublishResponse publishResponse = await _eventService.PublishEvent(new UserCreatedEvent() 74 | { 75 | Payload = userModel, 76 | CreateDate = DateTime.Now, 77 | Sender = $"Armut.Api-{nameof(UserController)}.{nameof(AddUser)}" 78 | }, _armutEventOptions.SnsTopic, cancellationToken); 79 | 80 | return CreatedAtAction("GetUser", new {user_id = userModel.Id}, userModel); 81 | } 82 | 83 | [HttpGet] 84 | [Route("{user_id}")] 85 | [ProducesResponseType(typeof(UserModel), StatusCodes.Status200OK)] 86 | public async Task GetUser([FromRoute(Name = "user_id")]int userId, CancellationToken cancellationToken) 87 | { 88 | UserModel userModel = await _userService.GetUserById(userId, cancellationToken); 89 | 90 | return Ok(userModel); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Armut.Api/Dockerfile: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base 4 | WORKDIR /app 5 | EXPOSE 80 6 | EXPOSE 443 7 | 8 | FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build 9 | WORKDIR /src 10 | COPY ["src/Armut.Api/Armut.Api.csproj", "src/Armut.Api/"] 11 | RUN dotnet restore "src/Armut.Api/Armut.Api.csproj" 12 | COPY . . 13 | WORKDIR "/src/src/Armut.Api" 14 | RUN dotnet build "Armut.Api.csproj" -c Release -o /app/build 15 | 16 | FROM build AS publish 17 | RUN dotnet publish "Armut.Api.csproj" -c Release -o /app/publish 18 | 19 | FROM base AS final 20 | WORKDIR /app 21 | COPY --from=publish /app/publish . 22 | ENTRYPOINT ["dotnet", "Armut.Api.dll"] -------------------------------------------------------------------------------- /src/Armut.Api/Options/ArmutEventOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Armut.Api.Options 7 | { 8 | public class ArmutEventOptions 9 | { 10 | public string SnsTopic { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Armut.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace Armut.Api 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Armut.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:11042", 7 | "sslPort": 44367 8 | } 9 | }, 10 | "$schema": "http://json.schemastore.org/launchsettings.json", 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "Armut.Api": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "swagger", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | }, 27 | "dotnetRunMessages": "true", 28 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 29 | }, 30 | "Docker": { 31 | "commandName": "Docker", 32 | "launchBrowser": true, 33 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", 34 | "publishAllPorts": true, 35 | "useSSL": true 36 | }, 37 | "WSL 2": { 38 | "commandName": "WSL2", 39 | "launchBrowser": true, 40 | "launchUrl": "https://localhost:5001/swagger", 41 | "environmentVariables": { 42 | "ASPNETCORE_URLS": "https://localhost:5001;http://localhost:5000", 43 | "ASPNETCORE_ENVIRONMENT": "Development" 44 | }, 45 | "distributionName": "" 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/Armut.Api/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | using Microsoft.OpenApi.Models; 7 | using Armut.Api.Core; 8 | using Armut.Api.Core.Installers; 9 | using Armut.Api.Core.Models; 10 | using Armut.Api.Options; 11 | using FluentValidation; 12 | using FluentValidation.AspNetCore; 13 | 14 | namespace Armut.Api 15 | { 16 | public class Startup 17 | { 18 | private IWebHostEnvironment WebHostEnvironment { get; } 19 | 20 | public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment) 21 | { 22 | WebHostEnvironment = webHostEnvironment; 23 | Configuration = configuration; 24 | } 25 | 26 | public IConfiguration Configuration { get; } 27 | 28 | // This method gets called by the runtime. Use this method to add services to the container. 29 | public void ConfigureServices(IServiceCollection services) 30 | { 31 | services.Configure(options => Configuration.GetSection("ArmutEvents")); 32 | services.AddControllers() 33 | .AddFluentValidation(configuration => 34 | { 35 | ValidatorOptions.Global.CascadeMode = CascadeMode.Stop; 36 | configuration.RegisterValidatorsFromAssemblyContaining(); 37 | }); 38 | 39 | services 40 | .AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo {Title = "Armut.Api", Version = "v1"}); }) 41 | .AddAutoMapper(typeof(MappingProfile)) 42 | .InstallServices(Configuration, WebHostEnvironment); 43 | } 44 | 45 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 46 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 47 | { 48 | if (env.IsDevelopment() || env.IsEnvironment("Testing")) 49 | { 50 | app.UseDeveloperExceptionPage(); 51 | app.UseSwagger(); 52 | app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Armut.Api v1")); 53 | } 54 | 55 | if (!env.IsEnvironment("Testing")) 56 | { 57 | app.UseHttpsRedirection(); 58 | } 59 | 60 | app.UseRouting(); 61 | 62 | app.UseEndpoints(endpoints => 63 | { 64 | endpoints.MapControllers(); 65 | }); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Armut.Api/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "LocalStack": { 10 | "UseLocalStack": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Armut.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "LocalStack": { 11 | "UseLocalStack": false 12 | }, 13 | "ArmutEvents": { 14 | "SnsTopic": "" 15 | }, 16 | "ConnectionStrings": { 17 | "Database": "host=localhost;port=5432;database=armutdb;username=postgres;password=Pass@word;" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Armut.EventProcessor/Armut.EventProcessor.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp3.1 4 | true 5 | Lambda 6 | 7 | 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Always 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Armut.EventProcessor/Function.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text.Json; 4 | using System.Threading.Tasks; 5 | using Amazon.Lambda.Core; 6 | using Amazon.Lambda.SNSEvents; 7 | using Armut.Api.Core; 8 | using Armut.Api.Core.Contracts; 9 | using Armut.Api.Core.Installers; 10 | using Armut.Api.Core.Models; 11 | using Armut.Api.Core.Models.Events; 12 | using Microsoft.EntityFrameworkCore; 13 | using Microsoft.Extensions.Configuration; 14 | using Microsoft.Extensions.DependencyInjection; 15 | using Microsoft.Extensions.FileProviders; 16 | using Microsoft.Extensions.Hosting; 17 | 18 | 19 | // Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. 20 | [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] 21 | 22 | namespace Armut.EventProcessor 23 | { 24 | public class Function 25 | { 26 | /// 27 | /// Default constructor. This constructor is used by Lambda to construct the instance. When invoked in a Lambda environment 28 | /// the AWS credentials will come from the IAM role associated with the function and the AWS region will be set to the 29 | /// region the Lambda function is executed in. 30 | /// 31 | public Function() 32 | { 33 | Configuration = new ConfigurationBuilder() 34 | .SetBasePath(Directory.GetCurrentDirectory()) 35 | .AddJsonFile("appsettings.json", false) 36 | .AddEnvironmentVariables() 37 | .Build(); 38 | 39 | HostEnvironment = new LambdaHostEnv() 40 | { 41 | ApplicationName = "Armut.EventProcessor", 42 | ContentRootFileProvider = new NullFileProvider(), 43 | EnvironmentName = "Production" 44 | }; 45 | 46 | var collection = new ServiceCollection(); 47 | 48 | ConfigureServices(collection); 49 | 50 | ServiceProvider = collection.BuildServiceProvider(); 51 | } 52 | 53 | private IConfiguration Configuration { get; } 54 | 55 | private IServiceProvider ServiceProvider { get; } 56 | 57 | private IHostEnvironment HostEnvironment { get; } 58 | 59 | 60 | /// 61 | /// This method is called for every Lambda invocation. This method takes in an SNS event object and can be used 62 | /// to respond to SNS messages. 63 | /// 64 | /// 65 | /// 66 | /// 67 | public async Task FunctionHandler(SNSEvent @event, ILambdaContext context) 68 | { 69 | try 70 | { 71 | foreach(SNSEvent.SNSRecord record in @event.Records) 72 | { 73 | await ProcessRecordAsync(record, context); 74 | 75 | string message = record.Sns.Message; 76 | var eventService = ServiceProvider.GetRequiredService(); 77 | 78 | if (TryDeserialize(message, context, out UserCreatedEvent userCreatedEvent)) 79 | { 80 | context.Logger.LogLine($"Saving UserCreatedEvent"); 81 | await eventService.SaveUserCreatedEvent(userCreatedEvent); 82 | } 83 | else if (TryDeserialize(message, context, out JobCreatedEvent jobCreatedEvent)) 84 | { 85 | context.Logger.LogLine($"Saving JobCreatedEvent"); 86 | await eventService.SaveJobCreatedEvent(jobCreatedEvent); 87 | } 88 | else 89 | { 90 | context.Logger.LogLine($"Invalid event ${message}"); 91 | } 92 | } 93 | } 94 | catch (Exception e) 95 | { 96 | context.Logger.LogLine(e.Message); 97 | throw; 98 | } 99 | } 100 | 101 | private void ConfigureServices(IServiceCollection serviceCollection) 102 | { 103 | serviceCollection 104 | .AddAutoMapper(typeof(MappingProfile)) 105 | .InstallServices(Configuration, HostEnvironment); 106 | } 107 | 108 | private static async Task ProcessRecordAsync(SNSEvent.SNSRecord record, ILambdaContext context) 109 | { 110 | context.Logger.LogLine($"Processed record {record.Sns.Message}"); 111 | 112 | // TODO: Do interesting work based on the new message 113 | await Task.CompletedTask; 114 | } 115 | 116 | private static bool TryDeserialize(string message, ILambdaContext context, out TEvent @event) 117 | { 118 | try 119 | { 120 | @event = JsonSerializer.Deserialize(message); 121 | } 122 | catch (Exception e) 123 | { 124 | context.Logger.LogLine(e.Message); 125 | @event = default(TEvent); 126 | return false; 127 | } 128 | 129 | return true; 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Armut.EventProcessor/LambdaHostEnv.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.FileProviders; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace Armut.EventProcessor 5 | { 6 | public class LambdaHostEnv : IHostEnvironment 7 | { 8 | public string EnvironmentName { get; set; } 9 | 10 | public string ApplicationName { get; set; } 11 | 12 | public string ContentRootPath { get; set; } 13 | 14 | public IFileProvider ContentRootFileProvider { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Armut.EventProcessor/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Mock Lambda Test Tool": { 4 | "commandName": "Executable", 5 | "commandLineArgs": "--port 5050", 6 | "workingDirectory": ".\\bin\\$(Configuration)\\netcoreapp3.1", 7 | "executablePath": "%USERPROFILE%\\.dotnet\\tools\\dotnet-lambda-test-tool-3.1.exe" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/Armut.EventProcessor/Readme.md: -------------------------------------------------------------------------------- 1 | # AWS Lambda Simple SNS Function Project 2 | 3 | This starter project consists of: 4 | * Function.cs - class file containing a class with a single function handler method 5 | * aws-lambda-tools-defaults.json - default argument settings for use with Visual Studio and command line deployment tools for AWS 6 | 7 | You may also have a test project depending on the options selected. 8 | 9 | The generated function handler responds to events on an Amazon SNS service. 10 | 11 | After deploying your function you must configure an Amazon SNS service as an event source to trigger your Lambda function. 12 | 13 | ## Here are some steps to follow from Visual Studio: 14 | 15 | To deploy your function to AWS Lambda, right click the project in Solution Explorer and select *Publish to AWS Lambda*. 16 | 17 | To view your deployed function open its Function View window by double-clicking the function name shown beneath the AWS Lambda node in the AWS Explorer tree. 18 | 19 | To perform testing against your deployed function use the Test Invoke tab in the opened Function View window. 20 | 21 | To configure event sources for your deployed function use the Event Sources tab in the opened Function View window. 22 | 23 | To update the runtime configuration of your deployed function use the Configuration tab in the opened Function View window. 24 | 25 | To view execution logs of invocations of your function use the Logs tab in the opened Function View window. 26 | 27 | ## Here are some steps to follow to get started from the command line: 28 | 29 | Once you have edited your template and code you can deploy your application using the [Amazon.Lambda.Tools Global Tool](https://github.com/aws/aws-extensions-for-dotnet-cli#aws-lambda-amazonlambdatools) from the command line. 30 | 31 | Install Amazon.Lambda.Tools Global Tools if not already installed. 32 | ``` 33 | dotnet tool install -g Amazon.Lambda.Tools 34 | ``` 35 | 36 | If already installed check if new version is available. 37 | ``` 38 | dotnet tool update -g Amazon.Lambda.Tools 39 | ``` 40 | 41 | Execute unit tests 42 | ``` 43 | cd "Armut.EventProcesser/test/Armut.EventProcesser.Tests" 44 | dotnet test 45 | ``` 46 | 47 | Deploy function to AWS Lambda 48 | ``` 49 | cd "Armut.EventProcesser/src/Armut.EventProcesser" 50 | dotnet lambda deploy-function 51 | ``` 52 | -------------------------------------------------------------------------------- /src/Armut.EventProcessor/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "Database": "host=localhost;port=5432;database=armutdb;username=postgres;password=Pass@word;" 4 | }, 5 | "LocalStack": { 6 | "UseLocalStack": true 7 | } 8 | } -------------------------------------------------------------------------------- /src/Armut.EventProcessor/aws-lambda-tools-defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "Information": [ 3 | "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", 4 | "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", 5 | "dotnet lambda help", 6 | "All the command line options for the Lambda command can be specified in this file." 7 | ], 8 | "profile": "new-cf", 9 | "region": "eu-central-1", 10 | "configuration": "Release", 11 | "framework": "netcoreapp3.1", 12 | "function-runtime": "dotnetcore3.1", 13 | "function-memory-size": 256, 14 | "function-timeout": 30, 15 | "function-handler": "Armut.EventProcessor::Armut.EventProcessor.Function::FunctionHandler" 16 | } -------------------------------------------------------------------------------- /src/Armut.EventProcessor/replace-localstack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sed -i -e 's+http://localhost+http://'"$LOCALSTACK_HOST"'+g' serverless.yml -------------------------------------------------------------------------------- /src/Armut.EventProcessor/serverless.yml: -------------------------------------------------------------------------------- 1 | service: processor-event 2 | 3 | plugins: 4 | - serverless-localstack 5 | 6 | custom: 7 | localstack: 8 | debug: true 9 | host: http://localhost 10 | stages: 11 | - local 12 | edgePort: 4566 13 | stages: 14 | - local 15 | - prod 16 | 17 | provider: 18 | name: aws 19 | runtime: dotnetcore3.1 20 | region: eu-central-1 21 | stackName: processor-lambda 22 | stage: ${opt:stage, 'local'} 23 | 24 | package: 25 | individually: true 26 | 27 | functions: 28 | processor: 29 | handler: Armut.EventProcessor::Armut.EventProcessor.Function::FunctionHandler 30 | package: 31 | artifact: artifact/processor-lambda-csharp.zip 32 | environment: 33 | ConnectionStrings__Database: ${env:CONN_STR} 34 | DOCKER_GATEWAY_HOST: ${env:DOCKER_GATEWAY_HOST} 35 | events: 36 | - sns: dispatch 37 | -------------------------------------------------------------------------------- /src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1 2 | 3 | RUN apt-get upgrade -yq && apt-get update && apt-get install -yq zip && apt-get install -yq curl && apt-get install -yq awscli 4 | RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - && apt-get install -yq nodejs 5 | 6 | ENV WAITFORIT_VERSION="v2.4.1" 7 | RUN curl -o /usr/local/bin/waitforit -sSL https://github.com/maxcnunes/waitforit/releases/download/$WAITFORIT_VERSION/waitforit-linux_amd64 \ 8 | && chmod +x /usr/local/bin/waitforit 9 | 10 | RUN npm install -g serverless@2.2.0 11 | RUN dotnet tool install --global Amazon.Lambda.Tools --version 3.3.1 && dotnet tool install --global LocalStack.AwsLocal --version 1.3.0 12 | ENV PATH="/root/.dotnet/tools:${PATH}" 13 | 14 | RUN mkdir -p /usr/tempprojs && mkdir -p /usr/tempprojs/Armut.EventProcessor && mkdir -p /usr/tempprojs/Armut.Api.Core 15 | COPY Armut.EventProcessor/Armut.EventProcessor.csproj /usr/tempprojs/Armut.EventProcessor 16 | COPY Armut.Api.Core/Armut.Api.Core.csproj /usr/tempprojs/Armut.Api.Core 17 | WORKDIR /usr/tempprojs/Armut.EventProcessor 18 | RUN dotnet restore 19 | 20 | WORKDIR / 21 | 22 | RUN mkdir -p /usr/src/Armut.EventProcessor 23 | WORKDIR /usr/src/Armut.EventProcessor 24 | RUN npm install serverless-localstack 25 | 26 | WORKDIR / 27 | 28 | COPY Armut.EventProcessor/Armut.EventProcessor.csproj /usr/src/Armut.EventProcessor/ 29 | COPY Armut.EventProcessor/Function.cs /usr/src/Armut.EventProcessor/ 30 | COPY Armut.EventProcessor/serverless.yml /usr/src/Armut.EventProcessor/ 31 | 32 | COPY . /usr/src/ 33 | 34 | WORKDIR /usr/src/Armut.EventProcessor 35 | 36 | RUN dotnet lambda package --configuration Release --framework netcoreapp3.1 --output-package artifact/processor-lambda-csharp.zip 37 | RUN chmod +x ./replace-localstack.sh 38 | 39 | CMD ./replace-localstack.sh && waitforit -address=http://"$LOCALSTACK_HOST":4566 -timeout=120 -- \ 40 | echo $CONN_STR \ 41 | && echo $DOCKER_GATEWAY_HOST \ 42 | && awslocal s3api list-buckets \ 43 | && serverless deploy --verbose --stage local -------------------------------------------------------------------------------- /tests/Armut.Api.Core.IntegrationTests/Armut.Api.Core.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Always 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/Armut.Api.Core.IntegrationTests/BaseTest.cs: -------------------------------------------------------------------------------- 1 | using Armut.Api.Core.Contracts; 2 | using Armut.Tests.Common.Fixtures; 3 | using Armut.Tests.Common.Seeders; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace Armut.Api.Core.IntegrationTests 8 | { 9 | public abstract class BaseTest 10 | { 11 | protected BaseTest(IntegrationTestFixture integrationTestFixture) 12 | { 13 | ConfigurationBuilder configurationBuilder = integrationTestFixture.CreateConfigureAppConfiguration(); 14 | Configuration = configurationBuilder.Build(); 15 | 16 | IServiceCollection serviceCollection = integrationTestFixture.CreateServiceCollection(Configuration); 17 | ServiceProvider = serviceCollection.BuildServiceProvider(); 18 | 19 | ArmutContext = ServiceProvider.GetRequiredService(); 20 | 21 | ArmutContext.Database.EnsureCreated(); 22 | Seeder.Seed(ArmutContext); 23 | 24 | UserService = ServiceProvider.GetRequiredService(); 25 | EventService = ServiceProvider.GetRequiredService(); 26 | } 27 | 28 | protected IConfiguration Configuration { get; } 29 | 30 | protected ServiceProvider ServiceProvider { get; } 31 | 32 | protected ArmutContext ArmutContext { get; } 33 | 34 | protected IUserService UserService { get; } 35 | 36 | protected IEventService EventService { get; } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Armut.Api.Core.IntegrationTests/CollectionDefinitions/IntegrationTestCollection.cs: -------------------------------------------------------------------------------- 1 | using Armut.Tests.Common.Fixtures; 2 | using Xunit; 3 | 4 | namespace Armut.Api.Core.IntegrationTests.CollectionDefinitions 5 | { 6 | [CollectionDefinition(nameof(IntegrationTestCollection))] 7 | public class IntegrationTestCollection : ICollectionFixture, ICollectionFixture 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/Armut.Api.Core.IntegrationTests/EventServiceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.Json; 6 | using System.Threading.Tasks; 7 | using Armut.Api.Core.Entities; 8 | using Armut.Api.Core.IntegrationTests.CollectionDefinitions; 9 | using Armut.Api.Core.Models; 10 | using Armut.Api.Core.Models.Events; 11 | using Armut.Tests.Common.Fixtures; 12 | using Microsoft.EntityFrameworkCore; 13 | using Xunit; 14 | 15 | namespace Armut.Api.Core.IntegrationTests 16 | { 17 | [Collection(nameof(IntegrationTestCollection))] 18 | public class EventServiceTests : BaseTest 19 | { 20 | public EventServiceTests(IntegrationTestFixture integrationTestFixture) 21 | : base(integrationTestFixture) 22 | { 23 | } 24 | 25 | 26 | [Fact] 27 | public async Task SaveUserCreatedEvent_Should_Create_An_Event_Record_In_Database() 28 | { 29 | var userCreatedEvent = new UserCreatedEvent() 30 | { 31 | Sender = nameof(EventServiceTests), 32 | Payload = new UserModel() 33 | { 34 | Id = 23, 35 | FirstName = "Deniz", 36 | LastName = "İrgin", 37 | Email = "den@armut.com", 38 | ProfilePictureUrl = Guid.NewGuid().ToString() 39 | }, 40 | CreateDate = DateTime.Now 41 | }; 42 | 43 | await EventService.SaveUserCreatedEvent(userCreatedEvent); 44 | 45 | EventEntity eventEntity = await ArmutContext.EventEntities.SingleOrDefaultAsync(entity => 46 | entity.EventType == nameof(UserCreatedEvent) && entity.EventRelationId == userCreatedEvent.Payload.Id); 47 | 48 | Assert.NotNull(eventEntity); 49 | 50 | UserModel userModel = JsonSerializer.Deserialize(eventEntity.Message); 51 | 52 | Assert.NotNull(userModel); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Armut.Api.Core.IntegrationTests/UserServiceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Armut.Api.Core.Entities; 4 | using Armut.Api.Core.IntegrationTests.CollectionDefinitions; 5 | using Armut.Api.Core.Models; 6 | using Armut.Tests.Common.Fixtures; 7 | using Microsoft.EntityFrameworkCore; 8 | using Xunit; 9 | 10 | namespace Armut.Api.Core.IntegrationTests 11 | { 12 | [Collection(nameof(IntegrationTestCollection))] 13 | public class UserServiceTests : BaseTest 14 | { 15 | public UserServiceTests(IntegrationTestFixture integrationTestFixture) 16 | : base(integrationTestFixture) 17 | { 18 | } 19 | 20 | [Fact] 21 | public async Task AddUser_Should_Throw_An_Exception_If_Given_AddUserModel_Is_Not_Valid() 22 | { 23 | await Assert.ThrowsAsync(async () => await UserService.AddUser(new AddUserModel())); 24 | } 25 | 26 | [Fact] 27 | public async Task AddUser_Should_Create_A_Record_In_Database() 28 | { 29 | var addUserModel = new AddUserModel() 30 | { 31 | FirstName = "Deniz", 32 | LastName = "İrgin", 33 | Email = "den@armut.com", 34 | ProfilePictureUrl = "sadasd" 35 | }; 36 | 37 | UserModel userModel = await UserService.AddUser(addUserModel); 38 | 39 | UserEntity userEntity = await ArmutContext.Users.SingleOrDefaultAsync(entity => entity.Id == userModel.Id); 40 | 41 | Assert.NotNull(userEntity); 42 | Assert.Equal(userModel.Id, userEntity.Id); 43 | Assert.Equal(userModel.FirstName, userEntity.FirstName); 44 | Assert.Equal(userModel.LastName, userEntity.LastName); 45 | Assert.Equal(userModel.Email, userEntity.Email); 46 | Assert.Equal(userModel.ProfilePictureUrl, userEntity.ProfilePictureUrl); 47 | } 48 | 49 | [Fact] 50 | public async Task GetUserById_Should_Read_A_Record_From_Database() 51 | { 52 | var userEntity = new UserEntity() 53 | { 54 | FirstName = "Fatma", 55 | LastName = "Tanrısevdi", 56 | Email = "fat@armut.com", 57 | CreateDate = DateTime.Now, 58 | ProfilePictureUrl = "sadas" 59 | }; 60 | 61 | await ArmutContext.AddAsync(userEntity); 62 | await ArmutContext.SaveChangesAsync(); 63 | 64 | UserModel userById = await UserService.GetUserById(userEntity.Id); 65 | 66 | Assert.NotNull(userById); 67 | Assert.Equal(userEntity.Id, userById.Id); 68 | Assert.Equal(userEntity.FirstName, userById.FirstName); 69 | Assert.Equal(userEntity.LastName, userById.LastName); 70 | Assert.Equal(userEntity.Email, userById.Email); 71 | Assert.Equal(userEntity.ProfilePictureUrl, userById.ProfilePictureUrl); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/Armut.Api.Core.IntegrationTests/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "Database": "host=localhost;port=5432;database=armutdb;username=postgres;password=Pass@word;" 4 | } 5 | } -------------------------------------------------------------------------------- /tests/Armut.Api.Core.Tests/Armut.Api.Core.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/Armut.Api.Core.Tests/UserServiceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using Xunit; 8 | 9 | namespace Armut.Api.Core.Tests 10 | { 11 | public class UserServiceTests 12 | { 13 | //[Fact] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Armut.Api.FunctionalTests/Armut.Api.FunctionalTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Always 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/Armut.Api.FunctionalTests/BaseScenario.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | using Amazon.Lambda; 8 | using Amazon.Lambda.Model; 9 | using Amazon.SimpleNotificationService; 10 | using Amazon.SimpleNotificationService.Model; 11 | using Armut.Api.Core; 12 | using Armut.Tests.Common.Fixtures; 13 | using Microsoft.AspNetCore.TestHost; 14 | using Microsoft.Extensions.DependencyInjection; 15 | 16 | namespace Armut.Api.FunctionalTests 17 | { 18 | public abstract class BaseScenario 19 | { 20 | protected BaseScenario(TestServerFixture testServerFixture) 21 | { 22 | TestServer = testServerFixture.Server; 23 | HttpClient = testServerFixture.CreateClient(); 24 | ArmutContext = TestServer.Services.GetRequiredService(); 25 | ServiceProvider = TestServer.Services; 26 | 27 | WaitUntilAwsResourcesReady().GetAwaiter().GetResult(); 28 | } 29 | 30 | protected TestServer TestServer { get; set; } 31 | 32 | protected HttpClient HttpClient { get; set; } 33 | 34 | protected ArmutContext ArmutContext { get; } 35 | 36 | protected IServiceProvider ServiceProvider { get; } 37 | 38 | protected async Task WaitUntilAwsResourcesReady() 39 | { 40 | var amazonSimpleNotificationService = ServiceProvider.GetRequiredService(); 41 | var amazonLambdaService = ServiceProvider.GetRequiredService(); 42 | 43 | var ready = false; 44 | var attempt = 0; 45 | do 46 | { 47 | await Task.Delay(500); 48 | try 49 | { 50 | ListTopicsResponse listTopicsResponse = await amazonSimpleNotificationService.ListTopicsAsync(new ListTopicsRequest()); 51 | ListFunctionsResponse listFunctionsResponse = await amazonLambdaService.ListFunctionsAsync(new ListFunctionsRequest()); 52 | 53 | if (listTopicsResponse.HttpStatusCode != HttpStatusCode.OK || listFunctionsResponse.HttpStatusCode != HttpStatusCode.OK) 54 | { 55 | attempt += 1; 56 | continue; 57 | } 58 | 59 | List topics = listTopicsResponse.Topics; 60 | List functionConfigurations = listFunctionsResponse.Functions; 61 | 62 | if (topics.Any(topic => topic.TopicArn.EndsWith("dispatch")) && functionConfigurations.Any()) 63 | { 64 | ready = true; 65 | break; 66 | } 67 | 68 | attempt += 1; 69 | } 70 | catch 71 | { 72 | attempt += 1; 73 | } 74 | 75 | } while (!ready || attempt <= 10); 76 | 77 | if (!ready) 78 | { 79 | throw new InvalidOperationException("The sns topic named dispatch was not found."); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/Armut.Api.FunctionalTests/CollectionDefinitions/ApiTestCollection.cs: -------------------------------------------------------------------------------- 1 | using Armut.Tests.Common.Fixtures; 2 | using Xunit; 3 | 4 | namespace Armut.Api.FunctionalTests.CollectionDefinitions 5 | { 6 | [CollectionDefinition(nameof(ApiTestCollection))] 7 | public class ApiTestCollection : ICollectionFixture, ICollectionFixture, ICollectionFixture, ICollectionFixture 8 | { 9 | } 10 | 11 | //[CollectionDefinition(nameof(ApiTestCollection))] 12 | //public class ApiTestCollection : ICollectionFixture, ICollectionFixture, ICollectionFixture 13 | //{ 14 | //} 15 | } -------------------------------------------------------------------------------- /tests/Armut.Api.FunctionalTests/Extensions/HttpContentExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Text; 3 | using System.Text.Json; 4 | using System.Threading.Tasks; 5 | 6 | namespace Armut.Api.FunctionalTests.Extensions 7 | { 8 | internal static class HttpContentExtensions 9 | { 10 | internal static async Task GetAsync(this HttpContent content) 11 | { 12 | var jsonSerializerOptions = new JsonSerializerOptions() {PropertyNamingPolicy = JsonNamingPolicy.CamelCase}; 13 | 14 | string json = await content.ReadAsStringAsync(); 15 | return JsonSerializer.Deserialize(json, jsonSerializerOptions); 16 | } 17 | } 18 | 19 | internal static class HttpClientExtensions 20 | { 21 | internal static Task PostAsync(this HttpClient client, string requestUri, TModel model) 22 | { 23 | string json = JsonSerializer.Serialize(model); 24 | var content = new StringContent(json, Encoding.UTF8, "application/json"); 25 | 26 | return client.PostAsync(requestUri, content); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Armut.Api.FunctionalTests/Routes/ApiVersion.cs: -------------------------------------------------------------------------------- 1 | namespace Armut.Api.FunctionalTests.Routes 2 | { 3 | internal struct ApiVersion 4 | { 5 | internal const string Version = "api"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/Armut.Api.FunctionalTests/Routes/UserRoots.cs: -------------------------------------------------------------------------------- 1 | namespace Armut.Api.FunctionalTests.Routes 2 | { 3 | internal class UserRoots 4 | { 5 | internal static readonly string Root = $"{ApiVersion.Version}/user"; 6 | 7 | internal static string GetUser(int userId) => $"{Root}/{userId}"; 8 | } 9 | } -------------------------------------------------------------------------------- /tests/Armut.Api.FunctionalTests/UserScenario.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Amazon.S3; 5 | using Amazon.S3.Model; 6 | using Armut.Api.Core.Entities; 7 | using Armut.Api.Core.Models; 8 | using Armut.Api.FunctionalTests.CollectionDefinitions; 9 | using Armut.Api.FunctionalTests.Extensions; 10 | using Armut.Api.FunctionalTests.Routes; 11 | using Armut.Tests.Common.Fixtures; 12 | using Armut.Tests.Common.Helpers; 13 | using Microsoft.EntityFrameworkCore; 14 | using Microsoft.Extensions.DependencyInjection; 15 | using Xunit; 16 | 17 | namespace Armut.Api.FunctionalTests 18 | { 19 | [Collection(nameof(ApiTestCollection))] 20 | public class UserScenario : BaseScenario 21 | { 22 | public UserScenario(TestServerFixture testServerFixture) 23 | : base(testServerFixture) 24 | { 25 | } 26 | 27 | [Fact] 28 | public async Task AddUser_Should_Return_201_And_UserModel() 29 | { 30 | var addUserModel = new AddUserViewModel() 31 | { 32 | FirstName = "Deniz", 33 | LastName = "Özgen", 34 | Email = "denolk@armut.com", 35 | ProfilePictureBase64 = ImageHelper.ImageSample1 36 | }; 37 | 38 | HttpResponseMessage httpResponseMessage = await HttpClient.PostAsync(UserRoots.Root, addUserModel); 39 | 40 | Assert.Equal(HttpStatusCode.Created, httpResponseMessage.StatusCode); 41 | 42 | var userModel = await httpResponseMessage.Content.GetAsync(); 43 | 44 | Assert.NotNull(userModel); 45 | Assert.Equal(addUserModel.FirstName, userModel.FirstName); 46 | Assert.Equal(addUserModel.LastName, userModel.LastName); 47 | Assert.Equal(addUserModel.Email, userModel.Email); 48 | } 49 | 50 | [Fact] 51 | public async Task AddUser_Should_Create_An_UserCreatedEvent_In_Database() 52 | { 53 | var addUserModel = new AddUserViewModel() 54 | { 55 | FirstName = "Ezgi", 56 | LastName = "Peker", 57 | Email = "ezg@armut.com", 58 | ProfilePictureBase64 = ImageHelper.ImageSample1 59 | }; 60 | 61 | HttpResponseMessage httpResponseMessage = await HttpClient.PostAsync(UserRoots.Root, addUserModel); 62 | 63 | Assert.Equal(HttpStatusCode.Created, httpResponseMessage.StatusCode); 64 | 65 | var userModel = await httpResponseMessage.Content.GetAsync(); 66 | Assert.NotNull(userModel); 67 | 68 | await Task.Delay(15000); 69 | 70 | EventEntity eventEntity = await ArmutContext.EventEntities.SingleOrDefaultAsync(entity => entity.EventRelationId == userModel.Id); 71 | Assert.NotNull(eventEntity); 72 | } 73 | 74 | [Fact] 75 | public async Task AddUser_Should_Save_A_Profile_Picture_To_A_Bucket() 76 | { 77 | var addUserModel = new AddUserViewModel() 78 | { 79 | FirstName = "Azmi", 80 | LastName = "Mengü", 81 | Email = "azn@armut.com", 82 | ProfilePictureBase64 = ImageHelper.ImageSample1 83 | }; 84 | 85 | HttpResponseMessage httpResponseMessage = await HttpClient.PostAsync(UserRoots.Root, addUserModel); 86 | 87 | Assert.Equal(HttpStatusCode.Created, httpResponseMessage.StatusCode); 88 | 89 | var userModel = await httpResponseMessage.Content.GetAsync(); 90 | string pictureUrl = $"http://localhost:4566/profile-pictures/{userModel.ProfilePictureUrl}"; 91 | 92 | var amazonS3 = ServiceProvider.GetRequiredService(); 93 | 94 | GetObjectResponse getObjectResponse = await amazonS3.GetObjectAsync(Constants.ProfilePictureBucket, userModel.ProfilePictureUrl); 95 | 96 | Assert.Equal(HttpStatusCode.OK, getObjectResponse.HttpStatusCode); 97 | Assert.Equal(userModel.ProfilePictureUrl, getObjectResponse.Key); 98 | } 99 | 100 | [Fact] 101 | public async Task AddUser_Should_Return_400_If_AddUserModel_Is_Invalid() 102 | { 103 | HttpResponseMessage httpResponseMessage = await HttpClient.PostAsync(UserRoots.Root, new AddUserViewModel()); 104 | 105 | string json = await httpResponseMessage.Content.ReadAsStringAsync(); 106 | 107 | Assert.NotNull(json); 108 | Assert.NotEmpty(json); 109 | Assert.Equal(HttpStatusCode.BadRequest, httpResponseMessage.StatusCode); 110 | } 111 | 112 | [Fact] 113 | public async Task GetUser_Should_Return_200_And_UserModel() 114 | { 115 | UserEntity userEntity = await ArmutContext.Users.FirstAsync(); 116 | 117 | HttpResponseMessage httpResponseMessage = await HttpClient.GetAsync(UserRoots.GetUser(userEntity.Id)); 118 | 119 | Assert.Equal(HttpStatusCode.OK, httpResponseMessage.StatusCode); 120 | 121 | var userModel = await httpResponseMessage.Content.GetAsync(); 122 | 123 | Assert.NotNull(userModel); 124 | Assert.Equal(userEntity.Id, userModel.Id); 125 | Assert.Equal(userEntity.FirstName, userModel.FirstName); 126 | Assert.Equal(userEntity.LastName, userModel.LastName); 127 | Assert.Equal(userEntity.Email, userModel.Email); 128 | Assert.Equal(userEntity.ProfilePictureUrl, userModel.ProfilePictureUrl); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /tests/Armut.Api.FunctionalTests/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "Database": "host=localhost;port=5432;database=armutdb;username=postgres;password=Pass@word;" 4 | }, 5 | "LocalStack": { 6 | "UseLocalStack": true 7 | }, 8 | "ArmutEvents": { 9 | "SnsTopic": "arn:aws:sns:eu-central-1:000000000000:dispatch" 10 | } 11 | } -------------------------------------------------------------------------------- /tests/Armut.EventProcessor.Tests/Armut.EventProcessor.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp3.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/Armut.EventProcessor.Tests/FunctionTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | using Xunit; 5 | using Amazon.Lambda.TestUtilities; 6 | using Amazon.Lambda.SNSEvents; 7 | using Armut.EventProcessor; 8 | 9 | namespace Armut.EventProcesser.Tests 10 | { 11 | public class FunctionTest 12 | { 13 | [Fact] 14 | public async Task TestSqsEventLambdaFunction() 15 | { 16 | var snsEvent = new SNSEvent 17 | { 18 | Records = new List 19 | { 20 | new SNSEvent.SNSRecord 21 | { 22 | Sns = new SNSEvent.SNSMessage() 23 | { 24 | Message = "foobar" 25 | } 26 | } 27 | } 28 | }; 29 | 30 | var logger = new TestLambdaLogger(); 31 | var context = new TestLambdaContext 32 | { 33 | Logger = logger 34 | }; 35 | 36 | var function = new Function(); 37 | await function.FunctionHandler(snsEvent, context); 38 | 39 | Assert.Contains("Processed record foobar", logger.Buffer.ToString()); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Armut.Tests.Common/Armut.Tests.Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/Armut.Tests.Common/Components/TestValidatorFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentValidation; 3 | 4 | namespace Armut.Tests.Common.Components 5 | { 6 | public class TestValidatorFactory : ValidatorFactoryBase { 7 | private readonly IServiceProvider _serviceProvider; 8 | 9 | public TestValidatorFactory(IServiceProvider serviceProvider) { 10 | _serviceProvider = serviceProvider; 11 | } 12 | 13 | public override IValidator CreateInstance(Type validatorType) { 14 | return _serviceProvider.GetService(validatorType) as IValidator; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Armut.Tests.Common/ContainerBuilder/TestcontainersBuilderWsl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using DotNet.Testcontainers.Clients; 6 | using DotNet.Testcontainers.Containers; 7 | using DotNet.Testcontainers.Containers.Builders; 8 | using DotNet.Testcontainers.Containers.Configurations; 9 | using DotNet.Testcontainers.Containers.OutputConsumers; 10 | using DotNet.Testcontainers.Containers.WaitStrategies; 11 | using DotNet.Testcontainers.Images; 12 | 13 | namespace Armut.Tests.Common.ContainerBuilder 14 | { 15 | public class TestcontainersBuilderWsl : ITestcontainersBuilder 16 | where TDockerContainer : IDockerContainer 17 | { 18 | private readonly TestcontainersBuilder _testcontainersBuilder; 19 | 20 | public TestcontainersBuilderWsl() 21 | { 22 | _testcontainersBuilder = new TestcontainersBuilder(); 23 | } 24 | 25 | public ITestcontainersBuilder ConfigureContainer(Action moduleConfiguration) 26 | { 27 | _testcontainersBuilder.ConfigureContainer(moduleConfiguration); 28 | return this; 29 | } 30 | 31 | public ITestcontainersBuilder WithImage(string image) 32 | { 33 | _testcontainersBuilder.WithImage(image); 34 | return this; 35 | } 36 | 37 | public ITestcontainersBuilder WithImage(IDockerImage image) 38 | { 39 | _testcontainersBuilder.WithImage(image); 40 | return this; 41 | } 42 | 43 | public ITestcontainersBuilder WithName(string name) 44 | { 45 | _testcontainersBuilder.WithName(name); 46 | return this; 47 | } 48 | 49 | public ITestcontainersBuilder WithWorkingDirectory(string workingDirectory) 50 | { 51 | _testcontainersBuilder.WithWorkingDirectory(workingDirectory); 52 | return this; 53 | } 54 | 55 | public ITestcontainersBuilder WithEntrypoint(params string[] entrypoint) 56 | { 57 | _testcontainersBuilder.WithEntrypoint(entrypoint); 58 | return this; 59 | } 60 | 61 | public ITestcontainersBuilder WithCommand(params string[] command) 62 | { 63 | _testcontainersBuilder.WithCommand(command); 64 | return this; 65 | } 66 | 67 | public ITestcontainersBuilder WithEnvironment(string name, string value) 68 | { 69 | _testcontainersBuilder.WithEnvironment(name, value); 70 | return this; 71 | } 72 | 73 | public ITestcontainersBuilder WithLabel(string name, string value) 74 | { 75 | _testcontainersBuilder.WithLabel(name, value); 76 | return this; 77 | } 78 | 79 | public ITestcontainersBuilder WithExposedPort(int port) 80 | { 81 | _testcontainersBuilder.WithExposedPort(port); 82 | return this; 83 | } 84 | 85 | public ITestcontainersBuilder WithExposedPort(string port) 86 | { 87 | _testcontainersBuilder.WithExposedPort(port); 88 | return this; 89 | } 90 | 91 | public ITestcontainersBuilder WithPortBinding(int port, bool assignRandomHostPort = false) 92 | { 93 | _testcontainersBuilder.WithPortBinding(port, assignRandomHostPort); 94 | return this; 95 | } 96 | 97 | public ITestcontainersBuilder WithPortBinding(int hostPort, int containerPort) 98 | { 99 | _testcontainersBuilder.WithPortBinding(hostPort, containerPort); 100 | return this; 101 | } 102 | 103 | public ITestcontainersBuilder WithPortBinding(string port, bool assignRandomHostPort = false) 104 | { 105 | _testcontainersBuilder.WithPortBinding(port, assignRandomHostPort); 106 | return this; 107 | } 108 | 109 | public ITestcontainersBuilder WithPortBinding(string hostPort, string containerPort) 110 | { 111 | return _testcontainersBuilder.WithPortBinding(hostPort, containerPort); 112 | } 113 | 114 | public ITestcontainersBuilder WithMount(string source, string destination) 115 | { 116 | var mounts = new IBind[] {new WslMount(source, destination, AccessMode.ReadWrite)}; 117 | return Build(_testcontainersBuilder, Apply(mounts: mounts)); 118 | } 119 | 120 | public ITestcontainersBuilder WithCleanUp(bool cleanUp) 121 | { 122 | _testcontainersBuilder.WithCleanUp(cleanUp); 123 | return this; 124 | } 125 | 126 | public ITestcontainersBuilder WithDockerEndpoint(string endpoint) 127 | { 128 | _testcontainersBuilder.WithDockerEndpoint(endpoint); 129 | return this; 130 | } 131 | 132 | public ITestcontainersBuilder WithRegistryAuthentication(string registryEndpoint, 133 | string username, string password) 134 | { 135 | _testcontainersBuilder.WithRegistryAuthentication(registryEndpoint, username, password); 136 | return this; 137 | } 138 | 139 | public ITestcontainersBuilder WithOutputConsumer(IOutputConsumer outputConsumer) 140 | { 141 | _testcontainersBuilder.WithOutputConsumer(outputConsumer); 142 | return this; 143 | } 144 | 145 | public ITestcontainersBuilder WithWaitStrategy(IWaitForContainerOS waitStrategy) 146 | { 147 | _testcontainersBuilder.WithWaitStrategy(waitStrategy); 148 | return this; 149 | } 150 | 151 | public TDockerContainer Build() 152 | { 153 | return _testcontainersBuilder.Build(); 154 | } 155 | 156 | private static ITestcontainersConfiguration Apply( 157 | Uri endpoint = null, 158 | IAuthenticationConfiguration authConfig = null, 159 | IDockerImage image = null, 160 | string name = null, 161 | string workingDirectory = null, 162 | IEnumerable entrypoint = null, 163 | IEnumerable command = null, 164 | IReadOnlyDictionary environments = null, 165 | IReadOnlyDictionary labels = null, 166 | IReadOnlyDictionary exposedPorts = null, 167 | IReadOnlyDictionary portBindings = null, 168 | IEnumerable mounts = null, 169 | IOutputConsumer outputConsumer = null, 170 | IEnumerable waitStrategies = null, 171 | bool cleanUp = true) 172 | { 173 | Type dockerApiEndpointType = typeof(TestcontainersConfiguration).Assembly.GetType("DotNet.Testcontainers.Clients.DockerApiEndpoint"); 174 | PropertyInfo localProperty = dockerApiEndpointType.GetProperties()[0]; 175 | var localEndpoint = localProperty.GetValue(null) as Uri; 176 | 177 | 178 | return new TestcontainersConfiguration( 179 | endpoint ?? localEndpoint, 180 | authConfig, 181 | image, 182 | name, 183 | workingDirectory, 184 | entrypoint, 185 | command, 186 | environments, 187 | labels, 188 | exposedPorts, 189 | portBindings, 190 | mounts, 191 | outputConsumer, 192 | waitStrategies, 193 | cleanUp); 194 | } 195 | 196 | private static ITestcontainersBuilder Build( 197 | TestcontainersBuilder previous, 198 | ITestcontainersConfiguration next, 199 | Action moduleConfiguration = null) 200 | { 201 | Type dockerApiEndpointType = typeof(TestcontainersConfiguration).Assembly.GetType("DotNet.Testcontainers.Clients.DockerApiEndpoint"); 202 | PropertyInfo localProperty = dockerApiEndpointType.GetProperties()[0]; 203 | var localEndpoint = localProperty.GetValue(null) as Uri; 204 | 205 | FieldInfo configurationFieldInfo = previous.GetType().GetField("configuration", BindingFlags.Instance | BindingFlags.NonPublic); 206 | FieldInfo moduleConfigurationFieldInfo = previous.GetType().GetField("moduleConfiguration", BindingFlags.Instance | BindingFlags.NonPublic); 207 | 208 | var previousConfiguration = (ITestcontainersConfiguration) configurationFieldInfo.GetValue(previous); 209 | var previousModuleConfiguration = (Action) moduleConfigurationFieldInfo.GetValue(previous); 210 | 211 | var cleanUp = next.CleanUp && previousConfiguration.CleanUp; 212 | var endpoint = Merge(next.Endpoint, previousConfiguration.Endpoint, localEndpoint); 213 | var image = Merge(next.Image, previousConfiguration.Image); 214 | var name = Merge(next.Name, previousConfiguration.Name); 215 | var workingDirectory = Merge(next.WorkingDirectory, previousConfiguration.WorkingDirectory); 216 | var entrypoint = Merge(next.Entrypoint, previousConfiguration.Entrypoint); 217 | var command = Merge(next.Command, previousConfiguration.Command); 218 | var environments = Merge(next.Environments, previousConfiguration.Environments); 219 | var labels = Merge(next.Labels, previousConfiguration.Labels); 220 | var exposedPorts = Merge(next.ExposedPorts, previousConfiguration.ExposedPorts); 221 | var portBindings = Merge(next.PortBindings, previousConfiguration.PortBindings); 222 | var mounts = Merge(next.Mounts, previousConfiguration.Mounts); 223 | 224 | var authConfig = new[] {next.AuthConfig, previousConfiguration.AuthConfig}.First(config => config != null); 225 | var outputConsumer = 226 | new[] {next.OutputConsumer, previousConfiguration.OutputConsumer}.First(config => config != null); 227 | var waitStrategies = 228 | new[] {next.WaitStrategies, previousConfiguration.WaitStrategies}.First(config => config != null); 229 | 230 | var mergedConfiguration = Apply( 231 | endpoint, 232 | authConfig, 233 | image, 234 | name, 235 | workingDirectory, 236 | entrypoint, 237 | command, 238 | environments, 239 | labels, 240 | exposedPorts, 241 | portBindings, 242 | mounts, 243 | outputConsumer, 244 | waitStrategies, 245 | cleanUp); 246 | 247 | ConstructorInfo constructorInfo = typeof(TestcontainersBuilder).GetConstructor( 248 | BindingFlags.NonPublic | BindingFlags.Instance, null, 249 | new[] {typeof(ITestcontainersConfiguration), typeof(Action)}, null); 250 | 251 | var testcontainersBuilder = (TestcontainersBuilder)constructorInfo.Invoke(new object[] {mergedConfiguration, moduleConfiguration ?? previousModuleConfiguration}); 252 | 253 | return testcontainersBuilder; 254 | } 255 | 256 | /// 257 | /// Returns the changed Testcontainer configuration object. If there is no change, the previous Testcontainer configuration object is returned. 258 | /// 259 | /// Changed Testcontainer configuration object. 260 | /// Previous Testcontainer configuration object. 261 | /// Default Testcontainer configuration. 262 | /// Any class. 263 | /// Changed Testcontainer configuration object. If there is no change, the previous Testcontainer configuration object. 264 | private static T Merge(T next, T previous, T defaultConfiguration = null) 265 | where T : class 266 | { 267 | return next == null || next.Equals(defaultConfiguration) ? previous : next; 268 | } 269 | 270 | /// 271 | /// Merges all existing and new Testcontainer configuration changes. If there are no changes, the previous Testcontainer configurations are returned. 272 | /// 273 | /// Changed Testcontainer configuration. 274 | /// Previous Testcontainer configuration. 275 | /// Type of . 276 | /// An updated Testcontainer configuration. 277 | private static IEnumerable Merge(IEnumerable next, IEnumerable previous) 278 | where T : class 279 | { 280 | if (next == null || previous == null) 281 | { 282 | return next ?? previous; 283 | } 284 | else 285 | { 286 | return next.Concat(previous).ToArray(); 287 | } 288 | } 289 | 290 | /// 291 | /// Merges all existing and new Testcontainer configuration changes. If there are no changes, the previous Testcontainer configurations are returned. 292 | /// 293 | /// Changed Testcontainer configuration. 294 | /// Previous Testcontainer configuration. 295 | /// Type of . 296 | /// An updated Testcontainer configuration. 297 | private static IReadOnlyDictionary Merge(IReadOnlyDictionary next, 298 | IReadOnlyDictionary previous) 299 | where T : class 300 | { 301 | if (next == null || previous == null) 302 | { 303 | return next ?? previous; 304 | } 305 | else 306 | { 307 | return next.Concat(previous.Where(item => !next.Keys.Contains(item.Key))) 308 | .ToDictionary(item => item.Key, item => item.Value); 309 | } 310 | } 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /tests/Armut.Tests.Common/ContainerBuilder/WslMount.cs: -------------------------------------------------------------------------------- 1 | using DotNet.Testcontainers.Containers.Configurations; 2 | 3 | namespace Armut.Tests.Common.ContainerBuilder 4 | { 5 | internal readonly struct WslMount : IBind 6 | { 7 | /// 8 | /// Creates a . 9 | /// 10 | /// The absolute path of the directory to mount on the host system. 11 | /// The absolute path of the directory to mount in the container. 12 | /// The Docker volume access mode. 13 | public WslMount(string hostPath, string containerPath, AccessMode accessMode) 14 | { 15 | this.HostPath = hostPath; 16 | this.ContainerPath = containerPath; 17 | this.AccessMode = accessMode; 18 | } 19 | 20 | /// 21 | public string HostPath { get; } 22 | 23 | /// 24 | public string ContainerPath { get; } 25 | 26 | /// 27 | public AccessMode AccessMode { get; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Armut.Tests.Common/Fixtures/DatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using DotNet.Testcontainers.Containers.Builders; 4 | using DotNet.Testcontainers.Containers.Configurations.Databases; 5 | using DotNet.Testcontainers.Containers.Modules; 6 | using DotNet.Testcontainers.Containers.Modules.Databases; 7 | using Xunit; 8 | 9 | namespace Armut.Tests.Common.Fixtures 10 | { 11 | public class DatabaseFixture : IAsyncLifetime 12 | { 13 | private readonly PostgreSqlTestcontainer _postgreSqlTestcontainer; 14 | private readonly TestcontainersContainer _pgadminTestContainer; 15 | 16 | public DatabaseFixture() 17 | { 18 | var databaseBuilder = new TestcontainersBuilder() 19 | .WithName($"postgres-integration-{DateTime.Now.Ticks}") 20 | .WithCleanUp(true) 21 | .WithDatabase(new PostgreSqlTestcontainerConfiguration("postgres:12.6") 22 | { 23 | Password = "Pass@word", 24 | Database = "armutdb", 25 | Username = "postgres", 26 | Port = 5432 27 | }) 28 | .WithEnvironment("DOCKER_GATEWAY_HOST", "172.17.0.1"); 29 | 30 | var pgadminTestContainerBuilder = new TestcontainersBuilder() 31 | .WithName($"pgadmin4-integration-{DateTime.Now.Ticks}") 32 | .WithImage("dpage/pgadmin4:4.30") 33 | .WithCleanUp(true) 34 | .WithEnvironment("PGADMIN_DEFAULT_EMAIL", "pgadmin4@pgadmin.org") 35 | .WithEnvironment("PGADMIN_DEFAULT_PASSWORD", "admin") 36 | .WithEnvironment("DOCKER_GATEWAY_HOST", "172.17.0.1") 37 | .WithPortBinding(5050, 80); 38 | 39 | _postgreSqlTestcontainer = databaseBuilder.Build(); 40 | _pgadminTestContainer = pgadminTestContainerBuilder.Build(); 41 | } 42 | 43 | public async Task InitializeAsync() 44 | { 45 | await _postgreSqlTestcontainer.StartAsync(); 46 | await _pgadminTestContainer.StartAsync(); 47 | } 48 | 49 | public async Task DisposeAsync() 50 | { 51 | await _postgreSqlTestcontainer.StopAsync(); 52 | await _pgadminTestContainer.StopAsync(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Armut.Tests.Common/Fixtures/FunctionDeployerFixture.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using DotNet.Testcontainers.Containers.Builders; 5 | using DotNet.Testcontainers.Containers.Modules; 6 | using DotNet.Testcontainers.Images.Builders; 7 | using Xunit; 8 | 9 | namespace Armut.Tests.Common.Fixtures 10 | { 11 | public class FunctionDeployerFixture : IAsyncLifetime 12 | { 13 | private readonly TestcontainersContainer _functionDeployerContainer; 14 | 15 | public FunctionDeployerFixture() 16 | { 17 | DirectoryInfo slnDirectory = TryGetSolutionDirectoryInfo(); 18 | string dockerFileDirectory = Path.Combine(slnDirectory.FullName, "src"); 19 | 20 | var functionDeployerBuilder = new ImageFromDockerfileBuilder() 21 | .WithName("event-processor-deployer") 22 | .WithDockerfileDirectory(dockerFileDirectory) 23 | .WithDeleteIfExists(false); 24 | 25 | string result = functionDeployerBuilder.Build().GetAwaiter().GetResult(); 26 | 27 | ITestcontainersBuilder functionDeployerContainer = 28 | new TestcontainersBuilder() 29 | .WithName("event-processor-deployer") 30 | .WithImage(result) 31 | .WithCleanUp(true) 32 | .WithEnvironment("DOCKER_GATEWAY_HOST", "172.17.0.1") 33 | .WithEnvironment("LOCALSTACK_HOST", "172.17.0.1") 34 | .WithEnvironment("SERVERLESS_STAGE", "local") 35 | .WithEnvironment("CONN_STR", "host=172.17.0.1;port=5432;database=armutdb;username=postgres;password=Pass@word;Timeout=30"); 36 | 37 | _functionDeployerContainer = functionDeployerContainer.Build(); 38 | } 39 | 40 | public async Task InitializeAsync() 41 | { 42 | await _functionDeployerContainer.StartAsync(); 43 | } 44 | 45 | public async Task DisposeAsync() 46 | { 47 | await _functionDeployerContainer.StopAsync(); 48 | } 49 | 50 | public static DirectoryInfo TryGetSolutionDirectoryInfo(string currentPath = null) 51 | { 52 | var directory = new DirectoryInfo(currentPath ?? Directory.GetCurrentDirectory()); 53 | while (directory != null && !directory.GetFiles("*.sln").Any()) 54 | { 55 | directory = directory.Parent; 56 | } 57 | return directory; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Armut.Tests.Common/Fixtures/IntegrationTestFixture.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Armut.Api.Core; 3 | using Armut.Api.Core.Installers; 4 | using Armut.Api.Core.Models.Validators; 5 | using Armut.Tests.Common.Components; 6 | using FluentValidation; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Hosting; 10 | using Moq; 11 | 12 | namespace Armut.Tests.Common.Fixtures 13 | { 14 | public class IntegrationTestFixture 15 | { 16 | public ConfigurationBuilder CreateConfigureAppConfiguration(string configFile = null) 17 | { 18 | var builder = new ConfigurationBuilder(); 19 | 20 | builder.SetBasePath(Directory.GetCurrentDirectory()); 21 | builder.AddJsonFile("appsettings.json", optional: true); 22 | if (!string.IsNullOrEmpty(configFile)) 23 | { 24 | builder.AddJsonFile(configFile, optional: true); 25 | } 26 | builder.AddEnvironmentVariables(); 27 | 28 | return builder; 29 | } 30 | 31 | public IServiceCollection CreateServiceCollection(IConfiguration configuration) 32 | { 33 | var serviceCollection = new ServiceCollection(); 34 | 35 | var mockEnvironment = new Mock(); 36 | mockEnvironment 37 | .Setup(m => m.EnvironmentName) 38 | .Returns("Hosting:UnitTestEnvironment"); 39 | 40 | //serviceCollection.AddLocalStack(configuration); 41 | 42 | ValidatorOptions.Global.CascadeMode = CascadeMode.Stop; 43 | 44 | serviceCollection 45 | .AddAutoMapper(typeof(MappingProfile)) 46 | .AddTransient() 47 | .AddValidatorsFromAssemblyContaining() 48 | .InstallServices(configuration, mockEnvironment.Object); 49 | 50 | return serviceCollection; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Armut.Tests.Common/Fixtures/LocalStackFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Runtime.CompilerServices; 5 | using System.Threading.Tasks; 6 | using Armut.Tests.Common.ContainerBuilder; 7 | using DotNet.Testcontainers.Containers.Builders; 8 | using DotNet.Testcontainers.Containers.Configurations; 9 | using DotNet.Testcontainers.Containers.Modules; 10 | using Xunit; 11 | 12 | namespace Armut.Tests.Common.Fixtures 13 | { 14 | public class LocalStackFixture : IAsyncLifetime 15 | { 16 | private readonly TestcontainersContainer _localStackContainer; 17 | 18 | public LocalStackFixture() 19 | { 20 | ITestcontainersBuilder localStackBuilder = 21 | new TestcontainersBuilder() 22 | .WithName($"LocalStack-0.12.6-{DateTime.Now.Ticks}") 23 | .WithImage("localstack/localstack:0.12.6") 24 | .WithMount("/var/run/docker.sock", "/var/run/docker.sock") 25 | .WithCleanUp(true) 26 | .WithEnvironment("DEFAULT_REGION", "eu-central-1") 27 | .WithEnvironment("DATA_DIR", "/tmp/localstack/data") 28 | .WithEnvironment("SERVICES", "iam,lambda,dynamodb,apigateway,s3,sns,cloudformation,cloudwatch,sts") 29 | .WithEnvironment("DOCKER_HOST", "unix:///var/run/docker.sock") 30 | .WithEnvironment("LAMBDA_EXECUTOR", "docker-reuse") 31 | .WithEnvironment("LS_LOG", "debug") 32 | .WithEnvironment("DOCKER_GATEWAY_HOST", "172.17.0.1") 33 | //.WithEnvironment("LAMBDA_DOCKER_DNS", "172.17.0.1") 34 | //.WithEnvironment("HOSTNAME", "172.17.0.1") 35 | //.WithEnvironment("HOSTNAME_EXTERNAL", "172.17.0.1") 36 | //.WithEnvironment("LOCALSTACK_HOSTNAME", "172.17.0.1") 37 | //.WithEnvironment("MAIN_CONTAINER_NAME", "LocalStack-0.12.6") 38 | .WithPortBinding(4566, 4566); 39 | 40 | _localStackContainer = localStackBuilder.Build(); 41 | ReplaceMountValue(_localStackContainer, new List() { new WslMount("/var/run/docker.sock", "/var/run/docker.sock", AccessMode.ReadWrite) }); 42 | } 43 | public async Task InitializeAsync() 44 | { 45 | await _localStackContainer.StartAsync(); 46 | } 47 | 48 | public async Task DisposeAsync() 49 | { 50 | await _localStackContainer.StopAsync(); 51 | } 52 | 53 | 54 | private static void ReplaceMountValue(TestcontainersContainer testcontainersContainer, IEnumerable wslMounts) 55 | { 56 | FieldInfo configurationFieldInfo = testcontainersContainer.GetType().GetField("configuration", BindingFlags.Instance | BindingFlags.NonPublic); 57 | var configuration = (ITestcontainersConfiguration) configurationFieldInfo.GetValue(testcontainersContainer); 58 | 59 | PropertyInfo propertyInfo = configuration 60 | .GetType() 61 | .GetProperty(nameof(configuration.Mounts), BindingFlags.Public | BindingFlags.Instance); 62 | 63 | FieldInfo backingField = GetBackingField(propertyInfo); 64 | 65 | backingField.SetValue(configuration, wslMounts); 66 | } 67 | 68 | private static FieldInfo GetBackingField(PropertyInfo pi) { 69 | if (!pi.CanRead || !pi.GetGetMethod(nonPublic:true).IsDefined(typeof(CompilerGeneratedAttribute), inherit:true)) 70 | return null; 71 | var backingField = pi.DeclaringType.GetField($"<{pi.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); 72 | if (backingField == null) 73 | return null; 74 | if (!backingField.IsDefined(typeof(CompilerGeneratedAttribute), inherit:true)) 75 | return null; 76 | return backingField; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/Armut.Tests.Common/Fixtures/TestServerFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Amazon.S3; 5 | using Amazon.S3.Model; 6 | using Armut.Api; 7 | using Armut.Api.Core; 8 | using Armut.Tests.Common.Seeders; 9 | using Microsoft.AspNetCore.Hosting; 10 | using Microsoft.AspNetCore.Mvc.Testing; 11 | using Microsoft.Extensions.Configuration; 12 | using Microsoft.Extensions.DependencyInjection; 13 | using Microsoft.Extensions.Hosting; 14 | 15 | namespace Armut.Tests.Common.Fixtures 16 | { 17 | public class TestServerFixture : WebApplicationFactory 18 | { 19 | protected override IHostBuilder CreateHostBuilder() 20 | { 21 | Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Testing"); 22 | //Environment.SetEnvironmentVariable("ASPNETCORE_URLS", "https://localhost:5001;http://localhost:5000"); 23 | 24 | var hostBuilder = base.CreateHostBuilder() 25 | .UseEnvironment("Testing") 26 | .ConfigureAppConfiguration(builder => 27 | { 28 | var configPath = Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json"); 29 | builder.AddJsonFile(configPath); 30 | }) 31 | .ConfigureServices((context, collection) => 32 | { 33 | ServiceProvider serviceProvider = collection.BuildServiceProvider(); 34 | 35 | var hostEnvironment = serviceProvider.GetRequiredService(); 36 | bool isTest = hostEnvironment.IsEnvironment("Testing"); 37 | 38 | if (!isTest) 39 | { 40 | throw new Exception("Incorrect config loaded."); 41 | } 42 | 43 | using IServiceScope scope = serviceProvider.CreateScope(); 44 | IServiceProvider scopeServiceProvider = scope.ServiceProvider; 45 | var armutContext = scopeServiceProvider.GetRequiredService(); 46 | var amazonS3Client = scopeServiceProvider.GetRequiredService(); 47 | 48 | armutContext.Database.EnsureCreated(); 49 | Seeder.Seed(armutContext); 50 | 51 | CreateS3BucketAsync(amazonS3Client, Constants.ProfilePictureBucket) 52 | .GetAwaiter() 53 | .GetResult(); 54 | }); 55 | 56 | return hostBuilder; 57 | } 58 | 59 | private async Task CreateS3BucketAsync(IAmazonS3 amazonS3Client, string bucketName) 60 | { 61 | //bool bucketExists = await amazonS3Client.DoesS3BucketExistAsync(bucketName); 62 | 63 | //if (!bucketExists) 64 | //{ 65 | await amazonS3Client.PutBucketAsync(new PutBucketRequest 66 | { 67 | BucketName = bucketName, 68 | }); 69 | 70 | await amazonS3Client.PutACLAsync(new PutACLRequest() 71 | { 72 | CannedACL = S3CannedACL.PublicRead, 73 | BucketName = bucketName 74 | }); 75 | //} 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/Armut.Tests.Common/Helpers/ImageHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Armut.Tests.Common.Helpers 2 | { 3 | public static class ImageHelper 4 | { 5 | public const string SinglePixelBlack = "R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw=="; 6 | 7 | public const string ImageSample1 = 8 | "/9j/4AAQSkZJRgABAQAAAQABAAD/4QDeRXhpZgAASUkqAAgAAAAGABIBAwABAAAAAQAAABoBBQABAAAAVgAAABsBBQABAAAAXgAAACgBAwABAAAAAgAAABMCAwABAAAAAQAAAGmHBAABAAAAZgAAAAAAAACAqQMA6AMAAICpAwDoAwAABwAAkAcABAAAADAyMTABkQcABAAAAAECAwCGkgcAFgAAAMAAAAAAoAcABAAAADAxMDABoAMAAQAAAP//AAACoAQAAQAAADIAAAADoAQAAQAAADIAAAAAAAAAQVNDSUkAAABQaWNzdW0gSUQ6IDcxOf/bAEMACAYGBwYFCAcHBwkJCAoMFA0MCwsMGRITDxQdGh8eHRocHCAkLicgIiwjHBwoNyksMDE0NDQfJzk9ODI8LjM0Mv/bAEMBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/CABEIADIAMgMBIgACEQEDEQH/xAAaAAACAwEBAAAAAAAAAAAAAAAAAwECBAUG/8QAFwEAAwEAAAAAAAAAAAAAAAAAAAECA//aAAwDAQACEAMQAAAB8ZToY4tMXipi0sTSPEdjlswZaOhWnSIemo9JlE+ly9mORjHMZejFzWQ0jXGgNsQAJAAAD//EACAQAAEEAgMAAwAAAAAAAAAAAAIAAQMREhMEECEUIDD/2gAIAQEAAQUCr630UDinGu6TRu61OpjdEQOvFSZZ0s0fHkxr2vAccdgsrvqf0XdnZg2L4r4jwYxU8MXHDbEuXNuIWCwOEDzDSc4k0k0btnH+H//EAB0RAAICAgMBAAAAAAAAAAAAAAABAhEQIRIgIjH/2gAIAQMBAT8BcWsUUybd7xdHIl9NHnt//8QAHREAAgICAwEAAAAAAAAAAAAAAAECERIhECAiQf/aAAgBAgEBPwFST4syRBL4WVZiiCpbNnrt/8QAJhAAAQIFAgYDAAAAAAAAAAAAAQACEBESITEDIhMgMkFRkSMwYf/aAAgBAQAGPwLnxz7nSWVaNhDiHBhNblaM3PfIdLXZQGDNUsN/1TOo0Dyvk1fSqaSSTgruqpoVJ0reFw6hjK6k3a0nvZYb6+j/xAAhEAADAAICAgIDAAAAAAAAAAAAAREhQTFhUXEQIDCB8f/aAAgBAQABPyEP6qYqqOaMhBfEOkXeLoPN/SGm5X4TsXEF+TzicvZ2T2eA+eS7g3dsekMVRkfFXmbd+ieFyHBbZqb4foTmWPZmWc05JmRGhhZCjlV6IT05BzW3kYMosbGUmaBJJFI76Y8FVVOsH8t+D//aAAwDAQACAAMAAAAQCe4/vORyOyP/APPQfP/EAB0RAQEAAgEFAAAAAAAAAAAAAAEAESFBIDFRYZH/2gAIAQMBAT8Q7wWIXF6p+j7BljgspmYMHK0HFrydP//EAB4RAQABAwUBAAAAAAAAAAAAAAEAEBExICFBYZGB/9oACAECAQE/EMQ0QZnZCG7yKCPJSUWLnEFJdZ8fdP8A/8QAIhABAQEAAgEEAwEBAAAAAAAAAREAITFREEFhcYGhwZHw/9oACAEBAAE/EGucJk0xx3okhvoZa+F2kPSYxitX53THpGMIeGhfxr5SfwavOMR2zAvJfgxcQPl1fZpyJyHtc9TluEoRH6YYZ7BEHIRB7Q41gB+TnDt8fWvm4SbxctkSgB4MvWjcLUKj3Xvjx95YfnrpA/8APbGJ2sAnnrCS4B3jAVvdMNOHbiNSF4/uWEMcnKOSIMS9Hg/3XCMLMTkD+7hIJRU0r+9YiQcfuYJZvedD/M8fEEmuc32HWe9PROsaHjf/2Q=="; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/Armut.Tests.Common/Seeders/ISeeder.cs: -------------------------------------------------------------------------------- 1 | using Armut.Api.Core; 2 | 3 | namespace Armut.Tests.Common.Seeders 4 | { 5 | public interface ISeeder 6 | { 7 | void Seed(ArmutContext context); 8 | } 9 | } -------------------------------------------------------------------------------- /tests/Armut.Tests.Common/Seeders/Seeder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Armut.Api.Core; 4 | 5 | namespace Armut.Tests.Common.Seeders 6 | { 7 | public static class Seeder 8 | { 9 | public static void Seed(ArmutContext context) 10 | { 11 | var installers = typeof(UserSeeder).Assembly.ExportedTypes 12 | .Where(m => typeof(ISeeder).IsAssignableFrom(m) && !m.IsInterface && !m.IsAbstract) 13 | .Select(Activator.CreateInstance) 14 | .Cast() 15 | .ToList(); 16 | 17 | installers.ForEach(m => m.Seed(context)); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /tests/Armut.Tests.Common/Seeders/UserSeeder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Mail; 3 | using Armut.Api.Core; 4 | using Armut.Api.Core.Entities; 5 | using AutoFixture; 6 | 7 | namespace Armut.Tests.Common.Seeders 8 | { 9 | public class UserSeeder : ISeeder 10 | { 11 | public void Seed(ArmutContext context) 12 | { 13 | var fixture = new Fixture(); 14 | fixture 15 | .Customize(c => c 16 | .With(x => x.Email, fixture.Create().Address)); 17 | 18 | fixture 19 | .Customize(c => c 20 | .With(x => x.Id, default(int))); 21 | 22 | 23 | IEnumerable userEntities = fixture.CreateMany(20); 24 | 25 | context.AddRange(userEntities); 26 | context.SaveChanges(); 27 | } 28 | } 29 | } 30 | --------------------------------------------------------------------------------