Skip to content

Commit 7a2f377

Browse files
authored
feat: add get endpoint (#22, #23)
1 parent b716ed9 commit 7a2f377

File tree

11 files changed

+639
-50
lines changed

11 files changed

+639
-50
lines changed

src/Captcha.Core/Constants.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ namespace Captcha.Core;
44

55
public static class Constants
66
{
7-
public const int MinCaptchaSize = 10;
8-
public const int MaxCaptchaSize = 1024;
97
public const int DefaultCaptchaWidth = 400;
108
public const int DefaultCaptchaHeight = 100;
119
public const float DefaultFrequency = 100F;
@@ -16,11 +14,15 @@ public static class Constants
1614
public const float WarpCaptchaTextFrequency = 4F;
1715
public const int CaptchaNoise = 50;
1816

19-
// According to https://learn.microsoft.com/en-us/dotnet/api/skiasharp.sktypeface, this should be thread-safe
17+
/// <summary>
18+
/// This object is shared across threads, however according to https://learn.microsoft.com/en-us/dotnet/api/skiasharp.sktypeface it is fine to share it across threads.
19+
/// </summary>
2020
public static SKTypeface MainFontTypeface { get; } =
2121
SKTypeface.FromStream(typeof(Constants).Assembly.GetManifestResourceStream("Captcha.Core.Resources.Fonts.Caveat-SemiBold.ttf"));
2222

23-
// According to https://learn.microsoft.com/en-us/dotnet/api/skiasharp.sktypeface, this should be thread-safe
23+
/// <summary>
24+
/// This object is shared across threads, however according to https://learn.microsoft.com/en-us/dotnet/api/skiasharp.sktypeface it is fine to share it across threads.
25+
/// </summary>
2426
public static SKTypeface FallbackFontTypeface { get; } =
2527
SKTypeface.FromStream(typeof(Constants).Assembly.GetManifestResourceStream("Captcha.Core.Resources.Fonts.Unifont.ttf"));
2628
}

src/Captcha.Core/Mappers/RequestToDomainMapper.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,23 @@ namespace Captcha.Core.Mappers;
66

77
public class RequestToDomainMapper
88
{
9-
public CaptchaConfigurationData ToDomain(CaptchaRequest request)
9+
public CaptchaConfigurationData ToDomain(GetCreateCaptchaRequest request)
10+
{
11+
var width = request.Width ?? Constants.DefaultCaptchaWidth;
12+
var height = request.Height ?? Constants.DefaultCaptchaHeight;
13+
14+
return new CaptchaConfigurationData
15+
{
16+
Text = request.Text,
17+
Width = width,
18+
Height = height,
19+
Frequency = GetFrequency(request.Difficulty, width, height),
20+
PrimaryColor = GetColor(request.Theme?.PrimaryColor) ?? Constants.DefaultPrimaryColor,
21+
SecondaryColor = GetColor(request.Theme?.SecondaryColor) ?? Constants.DefaultSecondaryColor,
22+
};
23+
}
24+
25+
public CaptchaConfigurationData ToDomain(PostCreateCaptchaRequest request)
1026
{
1127
var width = request.Width ?? Constants.DefaultCaptchaWidth;
1228
var height = request.Height ?? Constants.DefaultCaptchaHeight;

src/Captcha.Core/Models/CaptchaRequest.cs renamed to src/Captcha.Core/Models/GetCreateCaptchaRequest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace Captcha.Core.Models;
22

3-
public record CaptchaRequest
3+
public record GetCreateCaptchaRequest
44
{
55
public required string Text { get; init; }
66
public int? Width { get; init; }
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace Captcha.Core.Models;
2+
3+
public record PostCreateCaptchaRequest
4+
{
5+
public required string Text { get; init; }
6+
public int? Width { get; init; }
7+
public int? Height { get; init; }
8+
public CaptchaDifficulty? Difficulty { get; init; }
9+
public ThemeConfiguration? Theme { get; set; }
10+
}

src/Captcha.WebApi/Controllers/CaptchaController.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,37 @@ namespace Captcha.WebApi.Controllers;
1313
[Route("[controller]")]
1414
public class CaptchaController(ICaptchaImageService captchaImageService, RequestToDomainMapper requestToDomainMapper) : ControllerBase
1515
{
16+
[HttpGet]
17+
[ProducesResponseType(StatusCodes.Status200OK)]
18+
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
19+
[ProducesResponseType(StatusCodes.Status400BadRequest)]
20+
public async Task<FileContentResult> GetCreateAsync([FromQuery] GetCreateCaptchaRequest request)
21+
{
22+
var domain = requestToDomainMapper.ToDomain(request);
23+
24+
using var created = captchaImageService.CreateCaptchaImage(domain);
25+
26+
return await SerializeToJpegFile(created);
27+
}
28+
1629
[HttpPost]
1730
[ProducesResponseType(StatusCodes.Status200OK)]
1831
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
1932
[ProducesResponseType(StatusCodes.Status400BadRequest)]
20-
[SwaggerRequestExample(typeof(CaptchaRequest), typeof(CreateCaptchaExamples))]
21-
public async Task<FileContentResult> CreateAsync(CaptchaRequest request)
33+
[SwaggerRequestExample(typeof(PostCreateCaptchaRequest), typeof(CreateCaptchaExamples))]
34+
public async Task<FileContentResult> PostCreateAsync(PostCreateCaptchaRequest request)
2235
{
2336
var domain = requestToDomainMapper.ToDomain(request);
37+
2438
using var created = captchaImageService.CreateCaptchaImage(domain);
2539

40+
return await SerializeToJpegFile(created);
41+
}
42+
43+
private static async Task<FileContentResult> SerializeToJpegFile(SKBitmap image)
44+
{
2645
await using var memoryStream = new MemoryStream();
27-
SKImage.FromBitmap(created)
46+
SKImage.FromBitmap(image)
2847
.Encode(SKEncodedImageFormat.Jpeg, 100)
2948
.SaveTo(memoryStream);
3049

src/Captcha.WebApi/Controllers/Examples/CreateCaptchaExamples.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@ namespace Captcha.WebApi.Controllers.Examples;
33
using Captcha.Core.Models;
44
using Swashbuckle.AspNetCore.Filters;
55

6-
public record CreateCaptchaExamples : IMultipleExamplesProvider<CaptchaRequest>
6+
public record CreateCaptchaExamples : IMultipleExamplesProvider<PostCreateCaptchaRequest>
77
{
8-
public IEnumerable<SwaggerExample<CaptchaRequest>> GetExamples()
8+
public IEnumerable<SwaggerExample<PostCreateCaptchaRequest>> GetExamples()
99
{
1010
yield return SwaggerExample.Create(
1111
"Example 1",
1212
"Example 1 - Create captcha with text",
13-
new CaptchaRequest
13+
new PostCreateCaptchaRequest
1414
{
1515
Text = "hello world"
1616
});
1717

1818
yield return SwaggerExample.Create(
1919
"Example 2",
2020
"Example 2 - Create challenging captcha with text",
21-
new CaptchaRequest
21+
new PostCreateCaptchaRequest
2222
{
2323
Text = "hello world",
2424
Difficulty = CaptchaDifficulty.Challenging
@@ -27,7 +27,7 @@ public IEnumerable<SwaggerExample<CaptchaRequest>> GetExamples()
2727
yield return SwaggerExample.Create(
2828
"Example 3",
2929
"Example 3 - Create hard captcha with text",
30-
new CaptchaRequest
30+
new PostCreateCaptchaRequest
3131
{
3232
Text = "hello world",
3333
Difficulty = CaptchaDifficulty.Hard
@@ -36,7 +36,7 @@ public IEnumerable<SwaggerExample<CaptchaRequest>> GetExamples()
3636
yield return SwaggerExample.Create(
3737
"Example 4",
3838
"Example 4 - Create captcha with text and height and width",
39-
new CaptchaRequest
39+
new PostCreateCaptchaRequest
4040
{
4141
Text = "world",
4242
Height = 300,
@@ -46,7 +46,7 @@ public IEnumerable<SwaggerExample<CaptchaRequest>> GetExamples()
4646
yield return SwaggerExample.Create(
4747
"Example 5",
4848
"Example 5 - Create captcha with a color theme",
49-
new CaptchaRequest
49+
new PostCreateCaptchaRequest
5050
{
5151
Text = "hello world",
5252
Theme = new ThemeConfiguration
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
Feature: CaptchaGet
2+
I want to send different captcha requests and assure the image is generated
3+
4+
Scenario Outline: Send captcha requests
5+
Given I have a captcha request using get with following parameters:
6+
| Text | Width | Height | Difficulty |
7+
| <Text> | <Width> | <Height> | <Difficulty> |
8+
When I send the get request to the Create endpoint of the CaptchaController
9+
Then I expect a captcha image to be returned with the following attributes:
10+
| Width | Height |
11+
| <ExpectedWidth> | <ExpectedHeight> |
12+
Then I expect a captcha image to contain at least '<FirstColorPixels>' pixels of color '<FirstColorHex>' and at least '<SecondColorPixels>' pixels of color '<SecondColorHex>'
13+
14+
Examples:
15+
| Text | Width | Height | Difficulty | ExpectedWidth | ExpectedHeight | FirstColorPixels | FirstColorHex | SecondColorPixels | SecondColorHex |
16+
| مرحبًا | | | | 400 | 100 | 2300 | #FFD3D3D3 | 30000 | #FFFFFFFF |
17+
| 你好 | | | | 400 | 100 | 2500 | #FFD3D3D3 | 30000 | #FFFFFFFF |
18+
| こんにちは | | | | 400 | 100 | 2500 | #FFD3D3D3 | 30000 | #FFFFFFFF |
19+
| 안녕하세요 | | | | 400 | 100 | 3000 | #FFD3D3D3 | 30000 | #FFFFFFFF |
20+
| Здравствуйте | | | | 400 | 100 | 3000 | #FFD3D3D3 | 28000 | #FFFFFFFF |
21+
| Bonjour | | | | 400 | 100 | 3000 | #FFD3D3D3 | 30000 | #FFFFFFFF |
22+
| Guten Tag | | | | 400 | 100 | 3000 | #FFD3D3D3 | 30000 | #FFFFFFFF |
23+
| Selam | | | | 400 | 100 | 2500 | #FFD3D3D3 | 30000 | #FFFFFFFF |
24+
| Γεια σας | | | | 400 | 100 | 3000 | #FFD3D3D3 | 30000 | #FFFFFFFF |
25+
| Lorem | | | | 400 | 100 | 2500 | #FFD3D3D3 | 30000 | #FFFFFFFF |
26+
| Ipsum | | 200 | | 400 | 200 | 6000 | #FFD3D3D3 | 60000 | #FFFFFFFF |
27+
| helloworld | 200 | | Easy | 200 | 100 | 550 | #FFD3D3D3 | 10000 | #FFFFFFFF |
28+
| bar | 300 | 300 | Medium | 300 | 300 | 4500 | #FFD3D3D3 | 70000 | #FFFFFFFF |
29+
| foo | 400 | 400 | Hard | 400 | 400 | 6000 | #FFD3D3D3 | 110000 | #FFFFFFFF |
30+
| Ciao | 200 | | Easy | 200 | 100 | 850 | #FFD3D3D3 | 10000 | #FFFFFFFF |
31+
| Olá | 300 | 300 | Challenging | 300 | 300 | 6000 | #FFD3D3D3 | 64000 | #FFFFFFFF |
32+
| สวัสดี | 400 | 400 | Hard | 400 | 400 | 12000 | #FFD3D3D3 | 120000 | #FFFFFFFF |

tests/Captcha.FunctionalTests/Features/Captcha.feature renamed to tests/Captcha.FunctionalTests/Features/CaptchaPostEndpoint.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Feature: Captcha
1+
Feature: CaptchaPost
22
I want to send different captcha requests and assure the image is generated
33

44
Scenario Outline: Send captcha requests

tests/Captcha.FunctionalTests/StepDefinitions/CaptchaSteps.cs

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,17 @@ namespace Captcha.FunctionalTests.StepDefinitions;
1111
[Binding]
1212
public class CaptchaSteps(ScenarioContext context) : TestBase(context)
1313
{
14-
private CaptchaRequest? _request;
14+
private GetCreateCaptchaRequest? _getRequest;
15+
private PostCreateCaptchaRequest? _postRequest;
16+
1517
private RestResponse? _response;
1618

1719
[Given(@"I have a captcha request with following parameters:")]
1820
public void GivenIHaveACaptchaRequestWithFollowingParameters(Table table)
1921
{
2022
var row = table.Rows[0];
2123

22-
_request = new CaptchaRequest
24+
_postRequest = new PostCreateCaptchaRequest
2325
{
2426
Text = row[TestConstants.Text],
2527
Width = string.IsNullOrEmpty(row[TestConstants.Width])
@@ -50,7 +52,7 @@ public async Task WhenISendTheRequestToTheCreateEndpointOfTheCaptchaController()
5052
{
5153
RequestFormat = DataFormat.Json,
5254
Method = Method.Post
53-
}.AddJsonBody(_request);
55+
}.AddJsonBody(_postRequest);
5456

5557
_response = await Client.ExecuteAsync(request);
5658
}
@@ -128,4 +130,42 @@ public void ThenIExpectACaptchaImageToContainPixelsOfColorAndPixelsOfColor
128130
Assert.That(firstColorActualAmount, Is.AtLeast(firstColorExpectedAmount));
129131
Assert.That(secondColorActualAmount, Is.AtLeast(secondColorExpectedAmount));
130132
}
133+
134+
[Given("I have a captcha request using get with following parameters:")]
135+
public void GivenIHaveACaptchaRequestUsingGetWithFollowingParameters(Table table)
136+
{
137+
var row = table.Rows[0];
138+
139+
_getRequest = new GetCreateCaptchaRequest
140+
{
141+
Text = row[TestConstants.Text],
142+
Width = string.IsNullOrEmpty(row[TestConstants.Width])
143+
? null
144+
: int.Parse(row[TestConstants.Width], CultureInfo.InvariantCulture),
145+
Height = string.IsNullOrEmpty(row[TestConstants.Height])
146+
? null
147+
: int.Parse(row[TestConstants.Height], CultureInfo.InvariantCulture),
148+
Difficulty = string.IsNullOrEmpty(row[TestConstants.Difficulty])
149+
? null
150+
: Enum.Parse<CaptchaDifficulty>(row[TestConstants.Difficulty], true),
151+
Theme = new ThemeConfiguration()
152+
{
153+
PrimaryColor = !row.ContainsKey(TestConstants.PrimaryColor)
154+
? null
155+
: row[TestConstants.PrimaryColor],
156+
SecondaryColor = !row.ContainsKey(TestConstants.SecondaryColor)
157+
? null
158+
: row[TestConstants.SecondaryColor]
159+
}
160+
};
161+
}
162+
163+
[When("I send the get request to the Create endpoint of the CaptchaController")]
164+
public async Task WhenISendTheGetRequestToTheCreateEndpointOfTheCaptchaController()
165+
{
166+
var request = new RestRequest(TestConstants.CreateCaptchaEndpoint)
167+
.AddObject(_getRequest);
168+
169+
_response = await Client.ExecuteAsync(request);
170+
}
131171
}

tests/Captcha.UnitTests/ConstantsTests.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@ namespace Captcha.UnitTests;
77
[TestFixture]
88
public class ConstantsTests
99
{
10-
[Test]
11-
public void MaxCaptchaSizeShouldBe1024() => Assert.That(Constants.MaxCaptchaSize, Is.EqualTo(1024));
12-
13-
[Test]
14-
public void MinCaptchaSizeShouldBe10() => Assert.That(Constants.MinCaptchaSize, Is.EqualTo(10));
1510

1611
[Test]
1712
public void DefaultCaptchaWidthShouldBe400() => Assert.That(Constants.DefaultCaptchaWidth, Is.EqualTo(400));

0 commit comments

Comments
 (0)