├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .vscode
├── launch.json
└── tasks.json
├── CommandAPISolution.sln
├── azure-pipelines.yml
├── src
└── CommandAPI
│ ├── CommandAPI.csproj
│ ├── Controllers
│ └── CommandsController.cs
│ ├── Data
│ ├── CommandContext.cs
│ ├── ICommandAPIRepo.cs
│ ├── MockCommandAPIRepo.cs
│ └── SqlCommandAPIRepo.cs
│ ├── Dtos
│ ├── CommandCreateDto.cs
│ ├── CommandReadDto.cs
│ └── CommandUpdateDto.cs
│ ├── Migrations
│ ├── 20200524224711_AddCommandsToDB.Designer.cs
│ ├── 20200524224711_AddCommandsToDB.cs
│ └── CommandContextModelSnapshot.cs
│ ├── Models
│ └── Command.cs
│ ├── Profiles
│ └── CommandsProfile.cs
│ ├── Program.cs
│ ├── Properties
│ └── launchSettings.json
│ ├── Startup.cs
│ ├── appsettings.Development.json
│ └── appsettings.json
└── test
└── CommandAPI.Tests
├── CommandAPI.Tests.csproj
├── CommandTests.cs
└── CommandsControllerTests.cs
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | - master
3 |
4 | pool:
5 | vmImage: 'ubuntu-latest'
6 |
7 | variables:
8 | buildConfiguration: 'Release'
9 |
10 | steps:
11 | - task: UseDotNet@2
12 | - script: dotnet build --configuration $(buildConfiguration)
13 | displayName: 'dotnet build $(buildConfiguration)'
14 | - task: DotNetCoreCLI@2
15 | displayName: 'dotnet test'
16 | inputs:
17 | command: test
18 | projects: '**/*Tests/*.csproj'
19 | testRunTitle: 'xUNit Test Run'
20 |
21 | - task: DotNetCoreCLI@2
22 | displayName: 'dotnet publish'
23 | inputs:
24 | command: publish
25 | publishWebProjects: false
26 | projects: 'src/CommandAPI/*.csproj'
27 | arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'
28 |
29 | - task: PublishBuildArtifacts@1
30 | displayName: 'publish artifacts'
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | *.*~
3 | project.lock.json
4 | .DS_Store
5 | *.pyc
6 |
7 | # Visual Studio Code
8 | .VS Code
9 |
10 | # User-specific files
11 | *.suo
12 | *.user
13 | *.userosscache
14 | *.sln.docstates
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | build/
24 | bld/
25 | [Bb]in/
26 | [Oo]bj/
27 | msbuild.log
28 | msbuild.err
29 | msbuild.wrn
30 |
31 | # Visual Studio
32 | .vs/
33 |
34 | # Compiled Source
35 | *.com
36 | *.class
37 | *.dll
38 | *.exe
39 | *.o
40 | *.so
41 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | "program": "${workspaceFolder}/test/CommandAPI.Tests/bin/Debug/netcoreapp3.1/CommandAPI.Tests.dll",
13 | "args": [],
14 | "cwd": "${workspaceFolder}/test/CommandAPI.Tests",
15 | "console": "internalConsole",
16 | "stopAtEntry": false
17 | },
18 | {
19 | "name": ".NET Core Attach",
20 | "type": "coreclr",
21 | "request": "attach",
22 | "processId": "${command:pickProcess}"
23 | }
24 | ]
25 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/test/CommandAPI.Tests/CommandAPI.Tests.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/test/CommandAPI.Tests/CommandAPI.Tests.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "${workspaceFolder}/test/CommandAPI.Tests/CommandAPI.Tests.csproj",
36 | "/property:GenerateFullPaths=true",
37 | "/consoleloggerparameters:NoSummary"
38 | ],
39 | "problemMatcher": "$msCompile"
40 | }
41 | ]
42 | }
--------------------------------------------------------------------------------
/CommandAPISolution.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26124.0
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B6E4FE01-9307-4F3F-BEFE-EC991E035353}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandAPI", "src\CommandAPI\CommandAPI.csproj", "{9845AF6A-A083-44AF-B483-C853467CC662}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{48FF3F06-F3B1-4E65-A2E6-A5E52999C6B7}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandAPI.Tests", "test\CommandAPI.Tests\CommandAPI.Tests.csproj", "{57755B67-D6D1-43A5-A8AF-4C41FDC598EF}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Debug|x64 = Debug|x64
18 | Debug|x86 = Debug|x86
19 | Release|Any CPU = Release|Any CPU
20 | Release|x64 = Release|x64
21 | Release|x86 = Release|x86
22 | EndGlobalSection
23 | GlobalSection(SolutionProperties) = preSolution
24 | HideSolutionNode = FALSE
25 | EndGlobalSection
26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
27 | {9845AF6A-A083-44AF-B483-C853467CC662}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28 | {9845AF6A-A083-44AF-B483-C853467CC662}.Debug|Any CPU.Build.0 = Debug|Any CPU
29 | {9845AF6A-A083-44AF-B483-C853467CC662}.Debug|x64.ActiveCfg = Debug|Any CPU
30 | {9845AF6A-A083-44AF-B483-C853467CC662}.Debug|x64.Build.0 = Debug|Any CPU
31 | {9845AF6A-A083-44AF-B483-C853467CC662}.Debug|x86.ActiveCfg = Debug|Any CPU
32 | {9845AF6A-A083-44AF-B483-C853467CC662}.Debug|x86.Build.0 = Debug|Any CPU
33 | {9845AF6A-A083-44AF-B483-C853467CC662}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {9845AF6A-A083-44AF-B483-C853467CC662}.Release|Any CPU.Build.0 = Release|Any CPU
35 | {9845AF6A-A083-44AF-B483-C853467CC662}.Release|x64.ActiveCfg = Release|Any CPU
36 | {9845AF6A-A083-44AF-B483-C853467CC662}.Release|x64.Build.0 = Release|Any CPU
37 | {9845AF6A-A083-44AF-B483-C853467CC662}.Release|x86.ActiveCfg = Release|Any CPU
38 | {9845AF6A-A083-44AF-B483-C853467CC662}.Release|x86.Build.0 = Release|Any CPU
39 | {57755B67-D6D1-43A5-A8AF-4C41FDC598EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40 | {57755B67-D6D1-43A5-A8AF-4C41FDC598EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
41 | {57755B67-D6D1-43A5-A8AF-4C41FDC598EF}.Debug|x64.ActiveCfg = Debug|Any CPU
42 | {57755B67-D6D1-43A5-A8AF-4C41FDC598EF}.Debug|x64.Build.0 = Debug|Any CPU
43 | {57755B67-D6D1-43A5-A8AF-4C41FDC598EF}.Debug|x86.ActiveCfg = Debug|Any CPU
44 | {57755B67-D6D1-43A5-A8AF-4C41FDC598EF}.Debug|x86.Build.0 = Debug|Any CPU
45 | {57755B67-D6D1-43A5-A8AF-4C41FDC598EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
46 | {57755B67-D6D1-43A5-A8AF-4C41FDC598EF}.Release|Any CPU.Build.0 = Release|Any CPU
47 | {57755B67-D6D1-43A5-A8AF-4C41FDC598EF}.Release|x64.ActiveCfg = Release|Any CPU
48 | {57755B67-D6D1-43A5-A8AF-4C41FDC598EF}.Release|x64.Build.0 = Release|Any CPU
49 | {57755B67-D6D1-43A5-A8AF-4C41FDC598EF}.Release|x86.ActiveCfg = Release|Any CPU
50 | {57755B67-D6D1-43A5-A8AF-4C41FDC598EF}.Release|x86.Build.0 = Release|Any CPU
51 | EndGlobalSection
52 | GlobalSection(NestedProjects) = preSolution
53 | {9845AF6A-A083-44AF-B483-C853467CC662} = {B6E4FE01-9307-4F3F-BEFE-EC991E035353}
54 | {57755B67-D6D1-43A5-A8AF-4C41FDC598EF} = {48FF3F06-F3B1-4E65-A2E6-A5E52999C6B7}
55 | EndGlobalSection
56 | EndGlobal
57 |
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | - master
3 |
4 | pool:
5 | vmImage: 'ubuntu-latest'
6 |
7 | variables:
8 | buildConfiguration: 'Release'
9 |
10 | steps:
11 | - task: UseDotNet@2
12 | - script: dotnet build --configuration $(buildConfiguration)
13 | displayName: 'dotnet build $(buildConfiguration)'
14 | - task: DotNetCoreCLI@2
15 | displayName: 'dotnet test'
16 | inputs:
17 | command: test
18 | projects: '**/*Tests/*.csproj'
19 | testRunTitle: 'xUNit Test Run'
20 |
21 | - task: DotNetCoreCLI@2
22 | displayName: 'dotnet publish'
23 | inputs:
24 | command: publish
25 | publishWebProjects: false
26 | projects: 'src/CommandAPI/*.csproj'
27 | arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'
28 |
29 | - task: PublishBuildArtifacts@1
30 | displayName: 'publish artifacts'
31 |
32 |
--------------------------------------------------------------------------------
/src/CommandAPI/CommandAPI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 | cabd2435-bee0-47b0-8990-0889111a5d36
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 | all
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/CommandAPI/Controllers/CommandsController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using AutoMapper;
3 | using CommandAPI.Dtos;
4 | using CommandAPI.Data;
5 | using CommandAPI.Models;
6 | using System.Linq;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.AspNetCore.JsonPatch;
9 | using Microsoft.AspNetCore.Authorization;
10 |
11 | namespace CommandAPI.Controllers
12 | {
13 | [Route("api/[controller]")]
14 | [ApiController]
15 | public class CommandsController : ControllerBase
16 | {
17 |
18 | private readonly ICommandAPIRepo _repository;
19 | private readonly IMapper _mapper;
20 |
21 | public CommandsController(ICommandAPIRepo repository, IMapper mapper)
22 | {
23 | _repository = repository;
24 | _mapper = mapper;
25 | }
26 |
27 |
28 | //GET api/commands
29 |
30 | [HttpGet]
31 | public ActionResult> GetAllCommands()
32 | {
33 | var commandItems = _repository.GetAllCommands();
34 |
35 | return Ok(_mapper.Map>(commandItems));
36 | }
37 |
38 | //GET api/commands/{id}
39 | //[Authorize] //Apply this attribute to lockdown this ActionResult (or others)
40 | [HttpGet("{id}", Name = "GetCommandById")]
41 | public ActionResult GetCommandById(int id)
42 | {
43 | var commandItem = _repository.GetCommandById(id);
44 | if (commandItem == null)
45 | {
46 | return NotFound();
47 | }
48 | return Ok(_mapper.Map(commandItem));
49 | }
50 |
51 | //POST api/commands/
52 | [HttpPost]
53 | public ActionResult CreateCommand(CommandCreateDto commandCreateDto)
54 | {
55 | var commandModel = _mapper.Map(commandCreateDto);
56 | _repository.CreateCommand(commandModel);
57 | _repository.SaveChanges();
58 |
59 | var commandReadDto = _mapper.Map(commandModel);
60 |
61 | return CreatedAtRoute(nameof(GetCommandById), new { Id = commandReadDto.Id }, commandReadDto);
62 | }
63 |
64 | //PUT api/commands/{id}
65 | [HttpPut("{id}")]
66 | public ActionResult UpdateCommand(int id, CommandUpdateDto commandUpdateDto)
67 | {
68 | var commandModelFromRepo = _repository.GetCommandById(id);
69 | if (commandModelFromRepo == null)
70 | {
71 | return NotFound();
72 | }
73 | _mapper.Map(commandUpdateDto, commandModelFromRepo);
74 |
75 | _repository.UpdateCommand(commandModelFromRepo);
76 |
77 | _repository.SaveChanges();
78 |
79 | return NoContent();
80 | }
81 |
82 | //PATCH api/commands/{id}
83 | [HttpPatch("{id}")]
84 | public ActionResult PartialCommandUpdate(int id, JsonPatchDocument patchDoc)
85 | {
86 | var commandModelFromRepo = _repository.GetCommandById(id);
87 | if(commandModelFromRepo == null)
88 | {
89 | return NotFound();
90 | }
91 |
92 | var commandToPatch = _mapper.Map(commandModelFromRepo);
93 | patchDoc.ApplyTo(commandToPatch, ModelState);
94 |
95 | if(!TryValidateModel(commandToPatch))
96 | {
97 | return ValidationProblem(ModelState);
98 | }
99 |
100 | _mapper.Map(commandToPatch, commandModelFromRepo);
101 |
102 | _repository.UpdateCommand(commandModelFromRepo);
103 |
104 | _repository.SaveChanges();
105 |
106 | return NoContent();
107 | }
108 |
109 | //DELETE api/commands/{id}
110 | [HttpDelete("{id}")]
111 | public ActionResult DeleteCommand(int id)
112 | {
113 | var commandModelFromRepo = _repository.GetCommandById(id);
114 | if(commandModelFromRepo == null)
115 | {
116 | return NotFound();
117 | }
118 | _repository.DeleteCommand(commandModelFromRepo);
119 | _repository.SaveChanges();
120 |
121 | return NoContent();
122 | }
123 | }
124 | }
--------------------------------------------------------------------------------
/src/CommandAPI/Data/CommandContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using CommandAPI.Models;
3 |
4 | namespace CommandAPI.Data
5 | {
6 | public class CommandContext : DbContext
7 | {
8 | public CommandContext(DbContextOptions options) : base(options)
9 | {
10 |
11 | }
12 |
13 | public DbSet CommandItems {get; set;}
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/CommandAPI/Data/ICommandAPIRepo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using CommandAPI.Models;
3 |
4 | namespace CommandAPI.Data
5 | {
6 | public interface ICommandAPIRepo
7 | {
8 | bool SaveChanges();
9 |
10 | IEnumerable GetAllCommands();
11 | Command GetCommandById(int id);
12 | void CreateCommand(Command cmd);
13 | void UpdateCommand(Command cmd);
14 | void DeleteCommand(Command cmd);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/CommandAPI/Data/MockCommandAPIRepo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using CommandAPI.Models;
3 |
4 | namespace CommandAPI.Data
5 | {
6 | public class MockCommandAPIRepo : ICommandAPIRepo
7 | {
8 | public void CreateCommand(Command cmd)
9 | {
10 | throw new System.NotImplementedException();
11 | }
12 |
13 | public void DeleteCommand(Command cmd)
14 | {
15 | throw new System.NotImplementedException();
16 | }
17 |
18 | public IEnumerable GetAllCommands()
19 | {
20 | var commands = new List
21 | {
22 | new Command{
23 | Id=0, HowTo="How to genrate a migration",
24 | CommandLine="dotnet ef migrations add ",
25 | Platform=".Net Core EF"},
26 | new Command{
27 | Id=1, HowTo="Run Migrations",
28 | CommandLine="dotnet ef database update",
29 | Platform=".Net Core EF"},
30 | new Command{
31 | Id=2, HowTo="List active migrations",
32 | CommandLine="dotnet ef migrations list",
33 | Platform=".Net Core EF"}
34 | };
35 |
36 | return commands;
37 | }
38 |
39 | public Command GetCommandById(int id)
40 | {
41 | return new Command{
42 | Id=0, HowTo="How to genrate a migration",
43 | CommandLine="dotnet ef migrations add ",
44 | Platform=".Net Core EF"};
45 | }
46 |
47 | public bool SaveChanges()
48 | {
49 | throw new System.NotImplementedException();
50 | }
51 |
52 | public void UpdateCommand(Command cmd)
53 | {
54 | throw new System.NotImplementedException();
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/src/CommandAPI/Data/SqlCommandAPIRepo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using CommandAPI.Models;
5 |
6 | namespace CommandAPI.Data
7 | {
8 | public class SqlCommandAPIRepo : ICommandAPIRepo
9 | {
10 | private readonly CommandContext _context;
11 |
12 | public SqlCommandAPIRepo(CommandContext context)
13 | {
14 | _context = context;
15 | }
16 |
17 | public void CreateCommand(Command cmd)
18 | {
19 | if(cmd == null)
20 | {
21 | throw new ArgumentNullException(nameof(cmd));
22 | }
23 |
24 | _context.CommandItems.Add(cmd);
25 | }
26 |
27 | public void DeleteCommand(Command cmd)
28 | {
29 | if(cmd == null)
30 | {
31 | throw new ArgumentNullException(nameof(cmd));
32 | }
33 | _context.CommandItems.Remove(cmd);
34 | }
35 |
36 | public IEnumerable GetAllCommands()
37 | {
38 | return _context.CommandItems.ToList();
39 | }
40 |
41 | public Command GetCommandById(int id)
42 | {
43 | return _context.CommandItems.FirstOrDefault(p => p.Id == id);
44 | }
45 |
46 | public bool SaveChanges()
47 | {
48 | return (_context.SaveChanges() >= 0);
49 | }
50 |
51 | public void UpdateCommand(Command cmd)
52 | {
53 | //This does nothing
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/src/CommandAPI/Dtos/CommandCreateDto.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace CommandAPI.Dtos
4 | {
5 | public class CommandCreateDto
6 | {
7 | [Required]
8 | [MaxLength(250)]
9 | public string HowTo {get; set;}
10 |
11 | [Required]
12 | public string Platform {get; set;}
13 |
14 | [Required]
15 | public string CommandLine {get; set;}
16 | }
17 | }
--------------------------------------------------------------------------------
/src/CommandAPI/Dtos/CommandReadDto.cs:
--------------------------------------------------------------------------------
1 | namespace CommandAPI.Dtos
2 | {
3 | public class CommandReadDto
4 | {
5 | public int Id {get; set;}
6 |
7 | public string HowTo {get; set;}
8 |
9 | public string Platform {get; set;}
10 |
11 | public string CommandLine {get; set;}
12 | }
13 | }
--------------------------------------------------------------------------------
/src/CommandAPI/Dtos/CommandUpdateDto.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace CommandAPI.Dtos
4 | {
5 | public class CommandUpdateDto
6 | {
7 | [Required]
8 | [MaxLength(250)]
9 | public string HowTo {get; set;}
10 |
11 | [Required]
12 | public string Platform {get; set;}
13 |
14 | [Required]
15 | public string CommandLine {get; set;}
16 | }
17 | }
--------------------------------------------------------------------------------
/src/CommandAPI/Migrations/20200524224711_AddCommandsToDB.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using CommandAPI.Data;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Migrations;
6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
7 | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
8 |
9 | namespace CommandAPI.Migrations
10 | {
11 | [DbContext(typeof(CommandContext))]
12 | [Migration("20200524224711_AddCommandsToDB")]
13 | partial class AddCommandsToDB
14 | {
15 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
16 | {
17 | #pragma warning disable 612, 618
18 | modelBuilder
19 | .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
20 | .HasAnnotation("ProductVersion", "3.1.4")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 63);
22 |
23 | modelBuilder.Entity("CommandAPI.Models.Command", b =>
24 | {
25 | b.Property("Id")
26 | .ValueGeneratedOnAdd()
27 | .HasColumnType("integer")
28 | .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
29 |
30 | b.Property("CommandLine")
31 | .IsRequired()
32 | .HasColumnType("text");
33 |
34 | b.Property("HowTo")
35 | .IsRequired()
36 | .HasColumnType("character varying(250)")
37 | .HasMaxLength(250);
38 |
39 | b.Property("Platform")
40 | .IsRequired()
41 | .HasColumnType("text");
42 |
43 | b.HasKey("Id");
44 |
45 | b.ToTable("CommandItems");
46 | });
47 | #pragma warning restore 612, 618
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/CommandAPI/Migrations/20200524224711_AddCommandsToDB.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.Migrations;
2 | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
3 |
4 | namespace CommandAPI.Migrations
5 | {
6 | public partial class AddCommandsToDB : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.CreateTable(
11 | name: "CommandItems",
12 | columns: table => new
13 | {
14 | Id = table.Column(nullable: false)
15 | .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
16 | HowTo = table.Column(maxLength: 250, nullable: false),
17 | Platform = table.Column(nullable: false),
18 | CommandLine = table.Column(nullable: false)
19 | },
20 | constraints: table =>
21 | {
22 | table.PrimaryKey("PK_CommandItems", x => x.Id);
23 | });
24 | }
25 |
26 | protected override void Down(MigrationBuilder migrationBuilder)
27 | {
28 | migrationBuilder.DropTable(
29 | name: "CommandItems");
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/CommandAPI/Migrations/CommandContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using CommandAPI.Data;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
6 | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
7 |
8 | namespace CommandAPI.Migrations
9 | {
10 | [DbContext(typeof(CommandContext))]
11 | partial class CommandContextModelSnapshot : ModelSnapshot
12 | {
13 | protected override void BuildModel(ModelBuilder modelBuilder)
14 | {
15 | #pragma warning disable 612, 618
16 | modelBuilder
17 | .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
18 | .HasAnnotation("ProductVersion", "3.1.4")
19 | .HasAnnotation("Relational:MaxIdentifierLength", 63);
20 |
21 | modelBuilder.Entity("CommandAPI.Models.Command", b =>
22 | {
23 | b.Property("Id")
24 | .ValueGeneratedOnAdd()
25 | .HasColumnType("integer")
26 | .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
27 |
28 | b.Property("CommandLine")
29 | .IsRequired()
30 | .HasColumnType("text");
31 |
32 | b.Property("HowTo")
33 | .IsRequired()
34 | .HasColumnType("character varying(250)")
35 | .HasMaxLength(250);
36 |
37 | b.Property("Platform")
38 | .IsRequired()
39 | .HasColumnType("text");
40 |
41 | b.HasKey("Id");
42 |
43 | b.ToTable("CommandItems");
44 | });
45 | #pragma warning restore 612, 618
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/CommandAPI/Models/Command.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace CommandAPI.Models
4 | {
5 | public class Command
6 | {
7 | [Key]
8 | [Required]
9 | public int Id {get; set;}
10 |
11 | [Required]
12 | [MaxLength(250)]
13 | public string HowTo {get; set;}
14 |
15 | [Required]
16 | public string Platform {get; set;}
17 |
18 | [Required]
19 | public string CommandLine {get; set;}
20 | }
21 | }
--------------------------------------------------------------------------------
/src/CommandAPI/Profiles/CommandsProfile.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using CommandAPI.Dtos;
3 | using CommandAPI.Models;
4 |
5 | namespace CommandAPI.Profiles
6 | {
7 | public class CommandsProfile : Profile
8 | {
9 | public CommandsProfile()
10 | {
11 | //Source -> Target
12 | CreateMap();
13 | CreateMap();
14 | CreateMap();
15 | CreateMap();
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/CommandAPI/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Hosting;
6 | using Microsoft.Extensions.Configuration;
7 | using Microsoft.Extensions.Hosting;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace CommandAPI
11 | {
12 | public class Program
13 | {
14 | public static void Main(string[] args)
15 | {
16 | CreateHostBuilder(args).Build().Run();
17 | }
18 |
19 | public static IHostBuilder CreateHostBuilder(string[] args) =>
20 | Host.CreateDefaultBuilder(args)
21 | .ConfigureWebHostDefaults(webBuilder =>
22 | {
23 | webBuilder.UseStartup();
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/CommandAPI/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:10197",
7 | "sslPort": 44339
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "CommandAPI": {
19 | "commandName": "Project",
20 | "launchBrowser": true,
21 | "applicationUrl": "https://localhost:5001;http://localhost:5000",
22 | "environmentVariables": {
23 | "ASPNETCORE_ENVIRONMENT": "Development"
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/CommandAPI/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using CommandAPI.Data;
6 | using Microsoft.AspNetCore.Builder;
7 | using Microsoft.AspNetCore.Hosting;
8 | using Microsoft.AspNetCore.Http;
9 | using Microsoft.Extensions.Configuration;
10 | using Microsoft.Extensions.DependencyInjection;
11 | using Microsoft.Extensions.Hosting;
12 | using Microsoft.EntityFrameworkCore;
13 | using Npgsql;
14 | using AutoMapper;
15 | using Newtonsoft.Json.Serialization;
16 | using Microsoft.AspNetCore.Authentication.JwtBearer;
17 |
18 | namespace CommandAPI
19 | {
20 | public class Startup
21 | {
22 | public IConfiguration Configuration {get;}
23 | public Startup(IConfiguration configuration)
24 | {
25 | Configuration = configuration;
26 | }
27 |
28 | public void ConfigureServices(IServiceCollection services)
29 | {
30 | var builder = new NpgsqlConnectionStringBuilder();
31 | builder.ConnectionString =
32 | Configuration.GetConnectionString("PostgreSqlConnection");
33 | builder.Username = Configuration["UserID"];
34 | builder.Password = Configuration["Password"];
35 |
36 | services.AddDbContext(opt => opt.UseNpgsql(builder.ConnectionString));
37 |
38 | services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(opt =>
39 | {
40 | opt.Audience = Configuration["ResourceID"];
41 | opt.Authority = $"{Configuration["Instance"]}{Configuration["TenantId"]}";
42 | });
43 |
44 | services.AddControllers().AddNewtonsoftJson(s => {
45 | s.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
46 | });
47 |
48 | services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
49 |
50 | services.AddScoped();
51 | }
52 |
53 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env, CommandContext context)
54 | {
55 | context.Database.Migrate();
56 | if (env.IsDevelopment())
57 | {
58 | app.UseDeveloperExceptionPage();
59 | }
60 |
61 | app.UseRouting();
62 |
63 | app.UseAuthentication();
64 | app.UseAuthorization();
65 |
66 | app.UseEndpoints(endpoints =>
67 | {
68 | endpoints.MapControllers();
69 | });
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/CommandAPI/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "ConnectionStrings": {
10 | "PostgreSqlConnection":"Host=localhost;Port=5432;Database=CmdAPI;Pooling=true;"
11 | }
12 | }
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/CommandAPI/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "AllowedHosts": "*"
10 | }
--------------------------------------------------------------------------------
/test/CommandAPI.Tests/CommandAPI.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/test/CommandAPI.Tests/CommandTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Xunit;
3 | using CommandAPI.Models;
4 |
5 | namespace CommandAPI.Tests
6 | {
7 | public class CommandTests : IDisposable
8 | {
9 | Command testCommand;
10 |
11 | public CommandTests()
12 | {
13 | testCommand = new Command
14 | {
15 | HowTo = "Do something",
16 | Platform = "Some platform",
17 | CommandLine = "Some commandline"
18 | };
19 | }
20 |
21 | public void Dispose()
22 | {
23 | testCommand = null;
24 | }
25 |
26 |
27 | [Fact]
28 | public void CanChangeHowTo()
29 | {
30 | //Arrange
31 |
32 | //Act
33 | testCommand.HowTo = "Execute Unit Tests";
34 |
35 | //Assert
36 | Assert.Equal("Execute Unit Tests", testCommand.HowTo);
37 |
38 | }
39 |
40 | [Fact]
41 | public void CanChangePlatform()
42 | {
43 | //Arrange
44 |
45 | //Act
46 | testCommand.Platform = "xUnit";
47 |
48 | //Assert
49 | Assert.Equal("xUnit", testCommand.Platform);
50 |
51 | }
52 |
53 | [Fact]
54 | public void CanChangeCommandLine()
55 | {
56 | //Arrange
57 |
58 | //Act
59 | testCommand.CommandLine = "dotnet test";
60 |
61 | //Assert
62 | Assert.Equal("dotnet test", testCommand.CommandLine);
63 |
64 | }
65 |
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/test/CommandAPI.Tests/CommandsControllerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Moq;
4 | using AutoMapper;
5 | using CommandAPI.Models;
6 | using Xunit;
7 | using CommandAPI.Controllers;
8 | using CommandAPI.Data;
9 | using CommandAPI.Profiles;
10 | using Microsoft.AspNetCore.Mvc;
11 | using CommandAPI.Dtos;
12 |
13 | using Newtonsoft.Json.Serialization;
14 |
15 | namespace CommandAPI.Tests
16 | {
17 | public class CommandsControllerTests : IDisposable
18 | {
19 | Mock mockRepo;
20 | CommandsProfile realProfile;
21 | MapperConfiguration configuration;
22 | IMapper mapper;
23 |
24 | public CommandsControllerTests()
25 | {
26 | mockRepo = new Mock();
27 | realProfile = new CommandsProfile();
28 | configuration = new MapperConfiguration(cfg => cfg.AddProfile(realProfile));
29 | mapper = new Mapper(configuration);
30 | }
31 |
32 | public void Dispose()
33 | {
34 | mockRepo = null;
35 | mapper = null;
36 | configuration = null;
37 | realProfile = null;
38 | }
39 |
40 |
41 |
42 | //**************************************************
43 | //*
44 | //GET /api/commands Unit Tests
45 | //*
46 | //**************************************************
47 |
48 | //TEST 1.1
49 | [Fact]
50 | public void GetAllCommands_ReturnsZeroResources_WhenDBIsEmpty()
51 | {
52 | //Arrange
53 | mockRepo.Setup(repo =>
54 | repo.GetAllCommands()).Returns(GetCommands(0));
55 |
56 | var controller = new CommandsController(mockRepo.Object, mapper);
57 |
58 | //Act
59 | var result = controller.GetAllCommands();
60 |
61 | //Assert
62 | Assert.IsType(result.Result);
63 | }
64 |
65 | //TEST 1.2
66 | [Fact]
67 | public void GetAllCommands_ReturnsOneResource_WhenDBHasOneResource()
68 | {
69 | //Arrange
70 | mockRepo.Setup(repo =>
71 | repo.GetAllCommands()).Returns(GetCommands(1));
72 |
73 | var controller = new CommandsController(mockRepo.Object, mapper);
74 |
75 | //Act
76 | var result = controller.GetAllCommands();
77 |
78 | //Assert
79 | var okResult = result.Result as OkObjectResult;
80 |
81 | var commands = okResult.Value as List;
82 |
83 | Assert.Single(commands);
84 | }
85 |
86 | //TEST 1.3
87 | [Fact]
88 | public void GetAllCommands_Returns200OK_WhenDBHasOneResource()
89 | {
90 | //Arrange
91 | mockRepo.Setup(repo =>
92 | repo.GetAllCommands()).Returns(GetCommands(1));
93 |
94 | var controller = new CommandsController(mockRepo.Object, mapper);
95 |
96 | //Act
97 | var result = controller.GetAllCommands();
98 |
99 | //Assert
100 | Assert.IsType(result.Result);
101 |
102 | }
103 |
104 | //TEST 1.4
105 | [Fact]
106 | public void GetAllCommands_ReturnsCorrectType_WhenDBHasOneResource()
107 | {
108 | //Arrange
109 | mockRepo.Setup(repo =>
110 | repo.GetAllCommands()).Returns(GetCommands(1));
111 |
112 | var controller = new CommandsController(mockRepo.Object, mapper);
113 |
114 | //Act
115 | var result = controller.GetAllCommands();
116 |
117 | //Assert
118 | Assert.IsType>>(result);
119 | }
120 |
121 | //**************************************************
122 | //*
123 | //GET /api/commands/{id} Unit Tests
124 | //*
125 | //**************************************************
126 |
127 | //TEST 2.1
128 | [Fact]
129 | public void GetCommandByID_Returns404NotFound_WhenNonExistentIDProvided()
130 | {
131 | //Arrange
132 | mockRepo.Setup(repo =>
133 | repo.GetCommandById(0)).Returns(() => null);
134 |
135 | var controller = new CommandsController(mockRepo.Object, mapper);
136 |
137 | //Act
138 | var result = controller.GetCommandById(1);
139 |
140 | //Assert
141 | Assert.IsType(result.Result);
142 | }
143 |
144 | //TEST 2.2
145 | [Fact]
146 | public void GetCommandByID_Returns200OK__WhenValidIDProvided()
147 | {
148 | //Arrange
149 | mockRepo.Setup(repo =>
150 | repo.GetCommandById(1)).Returns(new Command { Id = 1, HowTo = "mock", Platform = "Mock", CommandLine = "Mock" });
151 |
152 | var controller = new CommandsController(mockRepo.Object, mapper);
153 |
154 | //Act
155 | var result = controller.GetCommandById(1);
156 |
157 | //Assert
158 | Assert.IsType(result.Result);
159 | }
160 |
161 | //TEST 2.3
162 | [Fact]
163 | public void GetCommandByID_ReturnsCorrectResouceType_WhenValidIDProvided()
164 | {
165 | //Arrange
166 | mockRepo.Setup(repo =>
167 | repo.GetCommandById(1)).Returns(new Command { Id = 1, HowTo = "mock", Platform = "Mock", CommandLine = "Mock" });
168 |
169 | var controller = new CommandsController(mockRepo.Object, mapper);
170 |
171 | //Act
172 | var result = controller.GetCommandById(1);
173 |
174 | //Assert
175 | Assert.IsType>(result);
176 | }
177 |
178 | //**************************************************
179 | //*
180 | //POST /api/commands/ Unit Tests
181 | //*
182 | //**************************************************
183 |
184 | //TEST 3.1
185 | [Fact]
186 | public void CreateCommand_ReturnsCorrectResourceType_WhenValidObjectSubmitted()
187 | {
188 | //Arrange
189 | mockRepo.Setup(repo =>
190 | repo.GetCommandById(1)).Returns(new Command { Id = 1, HowTo = "mock", Platform = "Mock", CommandLine = "Mock" });
191 |
192 | var controller = new CommandsController(mockRepo.Object, mapper);
193 |
194 | //Act
195 | var result = controller.CreateCommand(new CommandCreateDto { });
196 |
197 | //Assert
198 | Assert.IsType>(result);
199 | }
200 |
201 | //TEST 3.2
202 | [Fact]
203 | public void CreateCommand_Returns201Created_WhenValidObjectSubmitted()
204 | {
205 | //Arrange
206 | mockRepo.Setup(repo =>
207 | repo.GetCommandById(1)).Returns(new Command { Id = 1, HowTo = "mock", Platform = "Mock", CommandLine = "Mock" });
208 |
209 | var controller = new CommandsController(mockRepo.Object, mapper);
210 |
211 | //Act
212 | var result = controller.CreateCommand(new CommandCreateDto { });
213 |
214 | //Assert
215 | Assert.IsType(result.Result);
216 | }
217 |
218 |
219 | //**************************************************
220 | //*
221 | //PUT /api/commands/{id} Unit Tests
222 | //*
223 | //**************************************************
224 |
225 | //TEST 4.1
226 | [Fact]
227 | public void UpdateCommand_Returns204NoContent_WhenValidObjectSubmitted()
228 | {
229 | //Arrange
230 | mockRepo.Setup(repo =>
231 | repo.GetCommandById(1)).Returns(new Command { Id = 1, HowTo = "mock", Platform = "Mock", CommandLine = "Mock" });
232 |
233 | var controller = new CommandsController(mockRepo.Object, mapper);
234 |
235 | //Act
236 | var result = controller.UpdateCommand(1, new CommandUpdateDto { });
237 |
238 | //Assert
239 | Assert.IsType(result);
240 | }
241 |
242 |
243 | //TEST 4.2
244 | [Fact]
245 | public void UpdateCommand_Returns404NotFound_WhenNonExistentResourceIDSubmitted()
246 | {
247 | //Arrange
248 | mockRepo.Setup(repo =>
249 | repo.GetCommandById(0)).Returns(() => null);
250 |
251 | var controller = new CommandsController(mockRepo.Object, mapper);
252 |
253 | //Act
254 | var result = controller.UpdateCommand(0, new CommandUpdateDto { });
255 |
256 | //Assert
257 | Assert.IsType(result);
258 | }
259 |
260 |
261 | //**************************************************
262 | //*
263 | //PATCH /api/commands/{id} Unit Tests
264 | //*
265 | //**************************************************
266 |
267 |
268 | //TEST 5.1
269 | [Fact]
270 | public void PartialCommandUpdate_Returns404NotFound_WhenNonExistentResourceIDSubmitted()
271 | {
272 | //Arrange
273 | mockRepo.Setup(repo =>
274 | repo.GetCommandById(0)).Returns(() => null);
275 |
276 | var controller = new CommandsController(mockRepo.Object, mapper);
277 |
278 | //Act
279 | var result = controller.PartialCommandUpdate(0, new Microsoft.AspNetCore.JsonPatch.JsonPatchDocument { });
280 |
281 | //Assert
282 | Assert.IsType(result);
283 | }
284 |
285 |
286 | //**************************************************
287 | //*
288 | //DELETE /api/commands/{id} Unit Tests
289 | //*
290 | //**************************************************
291 |
292 | //TEST 6.1
293 | [Fact]
294 | public void DeleteCommand_Returns200OK_WhenValidResourceIDSubmitted()
295 | {
296 | //Arrange
297 | mockRepo.Setup(repo =>
298 | repo.GetCommandById(1)).Returns(new Command { Id = 1, HowTo = "mock", Platform = "Mock", CommandLine = "Mock" });
299 |
300 | var controller = new CommandsController(mockRepo.Object, mapper);
301 |
302 | //Act
303 | var result = controller.DeleteCommand(1);
304 |
305 | //Assert
306 | Assert.IsType(result);
307 | }
308 |
309 | //TEST 6.2
310 | [Fact]
311 | public void DeleteCommand_Returns_404NotFound_WhenNonExistentResourceIDSubmitted()
312 | {
313 | //Arrange
314 | mockRepo.Setup(repo =>
315 | repo.GetCommandById(0)).Returns(() => null);
316 |
317 | var controller = new CommandsController(mockRepo.Object, mapper);
318 |
319 | //Act
320 | var result = controller.DeleteCommand(0);
321 |
322 | //Assert
323 | Assert.IsType(result);
324 |
325 | }
326 |
327 | //**************************************************
328 | //*
329 | //Private Support Methods
330 | //*
331 | //**************************************************
332 |
333 |
334 |
335 |
336 | private List GetCommands(int num)
337 | {
338 | var commands = new List();
339 | if (num > 0)
340 | {
341 | commands.Add(new Command
342 | {
343 | Id = 0,
344 | HowTo = "How to genrate a migration",
345 | CommandLine = "dotnet ef migrations add ",
346 | Platform = ".Net Core EF"
347 | });
348 | }
349 | return commands;
350 | }
351 | }
352 | }
353 |
--------------------------------------------------------------------------------