├── .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 |
--------------------------------------------------------------------------------