Skip to content
This repository was archived by the owner on Sep 3, 2024. It is now read-only.

Commit 6ee4176

Browse files
Merge branch 'canonical-urls'
2 parents 49ad184 + ff85f9e commit 6ee4176

22 files changed

Lines changed: 210 additions & 61 deletions
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
namespace SqlStreamStore.HAL.Tests
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Net;
7+
using System.Threading.Tasks;
8+
using Shouldly;
9+
using SqlStreamStore.HAL.Resources;
10+
using Xunit;
11+
12+
public class CanonicalUrlTests
13+
{
14+
private const string StreamId = "a-stream";
15+
private readonly SqlStreamStoreHalMiddlewareFixture _fixture;
16+
17+
public CanonicalUrlTests()
18+
{
19+
_fixture = new SqlStreamStoreHalMiddlewareFixture();
20+
}
21+
22+
private static IEnumerable<string> GetQueryStrings(bool forward, bool prefetch)
23+
{
24+
IEnumerable<string[]> GetPermutations(string[] items, int length)
25+
=> length == 1
26+
? items.Select(item => new[] { item })
27+
: GetPermutations(items, length - 1)
28+
.SelectMany(permutation => items.Where(item => !permutation.Contains(item)),
29+
(t1, t2) => t1.Concat(new[] { t2 }).ToArray())
30+
;
31+
32+
var d = $"d={(forward ? 'f' : 'b')}";
33+
var m = "m=20";
34+
var e = prefetch ? "e" : null;
35+
var p = "p=0";
36+
37+
var parameters = new[]
38+
{
39+
d, m, e, p
40+
};
41+
42+
return
43+
from set in GetPermutations(parameters, 4)
44+
select string.Join('&', set.Where(x => x != null));
45+
}
46+
47+
private static IEnumerable<(string, Uri)> NonCanonical()
48+
{
49+
var formatters = new Dictionary<bool, Func<string, int, long, bool, string>>
50+
{
51+
[true] = LinkFormatter.FormatForwardLink,
52+
[false] = LinkFormatter.FormatBackwardLink
53+
};
54+
55+
(string streamId, string path)[] streams =
56+
{
57+
(StreamId, $"/streams/{StreamId}"),
58+
(Constants.Streams.All, Constants.Streams.All)
59+
};
60+
61+
return
62+
from _ in streams
63+
from prefetch in new[] { true, false }
64+
from forward in new[] { true, false }
65+
let format = formatters[forward]
66+
let canonicalUri = format(_.streamId, 20, 0, prefetch)
67+
from queryString in GetQueryStrings(forward, prefetch)
68+
// query strings are supposed to be case sensitive!!
69+
//.Concat(new[] { $"d={(forward ? 'F' : 'B')}&M=20&P=0{(prefetch ? "&E" : string.Empty)}" })
70+
where $"{_.streamId}?{queryString}" != canonicalUri
71+
select ($"{_.path}?{queryString}", new Uri(
72+
canonicalUri,
73+
UriKind.Relative));
74+
}
75+
76+
public static IEnumerable<object[]> NonCanonicalUriCases()
77+
=> NonCanonical()
78+
.Select(x => new object[] { x.Item1, x.Item2 })
79+
.ToArray();
80+
81+
[Theory, MemberData(nameof(NonCanonicalUriCases))]
82+
public async Task non_canonical_uri(string requestUri, Uri expectedLocation)
83+
{
84+
using(var response = await _fixture.HttpClient.GetAsync(requestUri))
85+
{
86+
response.StatusCode.ShouldBe(HttpStatusCode.PermanentRedirect);
87+
response.Headers.Location.ShouldBe(expectedLocation);
88+
}
89+
}
90+
}
91+
}

src/SqlStreamStore.HAL.Tests/HeadersTests.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using System.Net.Http;
66
using System.Threading.Tasks;
77
using Shouldly;
8+
using SqlStreamStore.HAL.Resources;
9+
using SqlStreamStore.Streams;
810
using Xunit;
911

1012
public class HeadersTests : IDisposable
@@ -32,7 +34,10 @@ public async Task all_stream_head_link(HttpMethod method)
3234

3335
var position = await _fixture.StreamStore.ReadHeadPosition();
3436

35-
using(var response = await _fixture.HttpClient.SendAsync(new HttpRequestMessage(method, "/stream")))
37+
using(var response = await _fixture.HttpClient.SendAsync(
38+
new HttpRequestMessage(
39+
method,
40+
LinkFormatter.FormatBackwardLink("/stream", 20, Position.End, true))))
3641
{
3742
response.IsSuccessStatusCode.ShouldBeTrue();
3843

src/SqlStreamStore.HAL.Tests/SqlStreamStore.HAL.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<DebugType>portable</DebugType>
1414
<DebugSymbols>true</DebugSymbols>
1515
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
16+
<NoWarn>1591</NoWarn>
1617
</PropertyGroup>
1718
<ItemGroup>
1819
<ProjectReference Include="..\SqlStreamStore.HAL\SqlStreamStore.HAL.csproj" />

src/SqlStreamStore.HAL.Tests/StreamNavigationTests.cs

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ public static IEnumerable<object[]> GetNoMessagesPagingCases()
3333
[Theory, MemberData(nameof(GetNoMessagesPagingCases))]
3434
public async Task read_head_link_no_messages(string stream, string baseAddress, HttpStatusCode statusCode)
3535
{
36-
using(var response = await _fixture.HttpClient.GetAsync($"{baseAddress}{stream}"))
36+
using(var firstResponse = await _fixture.HttpClient.GetAsync($"{baseAddress}{stream}"))
37+
using(var response = await _fixture.HttpClient.GetAsync($"{baseAddress}{firstResponse.Headers.Location}"))
3738
{
3839
response.StatusCode.ShouldBe(statusCode);
3940

@@ -65,25 +66,31 @@ public async Task read_head_link_when_multiple_pages(string stream, string baseA
6566
{
6667
await _fixture.WriteNMessages("a-stream", 30);
6768

68-
using(var response = await _fixture.HttpClient.GetAsync($"{baseAddress}{stream}"))
69+
using(var firstResponse = await _fixture.HttpClient.GetAsync($"{baseAddress}{stream}"))
6970
{
70-
response.StatusCode.ShouldBe(HttpStatusCode.OK);
71+
firstResponse.StatusCode.ShouldBe(HttpStatusCode.PermanentRedirect);
7172

72-
var resource = await response.AsHal();
73+
using(var response =
74+
await _fixture.HttpClient.GetAsync($"{baseAddress}{firstResponse.Headers.Location}"))
75+
{
76+
response.StatusCode.ShouldBe(HttpStatusCode.OK);
7377

74-
resource.ShouldLink(Constants.Relations.Self, $"{stream}?{LastLinkQuery}");
78+
var resource = await response.AsHal();
7579

76-
resource.ShouldLink(Constants.Relations.Last, $"{stream}?{LastLinkQuery}");
80+
resource.ShouldLink(Constants.Relations.Self, $"{stream}?{LastLinkQuery}");
7781

78-
resource.ShouldLink(Constants.Relations.Previous, $"{stream}?d=b&m=20&p=9");
82+
resource.ShouldLink(Constants.Relations.Last, $"{stream}?{LastLinkQuery}");
7983

80-
resource.ShouldLink(Constants.Relations.First, $"{stream}?{FirstLinkQuery}");
84+
resource.ShouldLink(Constants.Relations.Previous, $"{stream}?d=b&m=20&p=9");
8185

82-
resource.ShouldLink(Constants.Relations.Feed, $"{stream}?{LastLinkQuery}");
86+
resource.ShouldLink(Constants.Relations.First, $"{stream}?{FirstLinkQuery}");
8387

84-
if(!IsAllStream($"{baseAddress}{stream}"))
85-
{
86-
resource.ShouldLink(Constants.Relations.Metadata, $"{stream}/metadata");
88+
resource.ShouldLink(Constants.Relations.Feed, $"{stream}?{LastLinkQuery}");
89+
90+
if(!IsAllStream($"{baseAddress}{stream}"))
91+
{
92+
resource.ShouldLink(Constants.Relations.Metadata, $"{stream}/metadata");
93+
}
8794
}
8895
}
8996
}
@@ -149,7 +156,8 @@ public async Task read_stream_should_include_the_last_position_and_version()
149156

150157
var page = await _fixture.StreamStore.ReadStreamForwards("a-stream", StreamVersion.Start, 10, false);
151158

152-
using(var response = await _fixture.HttpClient.GetAsync("/streams/a-stream"))
159+
using(var firstResponse = await _fixture.HttpClient.GetAsync("/streams/a-stream"))
160+
using(var response = await _fixture.HttpClient.GetAsync($"/streams/{firstResponse.Headers.Location}"))
153161
{
154162
response.StatusCode.ShouldBe(HttpStatusCode.OK);
155163

src/SqlStreamStore.HAL/AllStreamOptionsMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ private static Action<IApplicationBuilder> ConfigureOptions(IResource resource)
3434

3535
private static MidFunc Options(IResource resource) => (context, next) =>
3636
{
37-
context.SetStandardCorsHeaders(resource.Options);
37+
context.SetStandardCorsHeaders(resource.Allowed);
3838

3939
return Task.CompletedTask;
4040
};

src/SqlStreamStore.HAL/AppendStreamMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ private static MidFunc AppendStream(StreamResource stream) => async (context, ne
2525
{
2626
var options = await AppendStreamOperation.Create(context.Request, context.RequestAborted);
2727

28-
var response = await stream.AppendMessages(options, context.RequestAborted);
28+
var response = await stream.Post(options, context.RequestAborted);
2929

3030
await context.WriteHalResponse(response);
3131
};

src/SqlStreamStore.HAL/Constants.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public static class Headers
88
public const string HeadPosition = "SSS-HeadPosition";
99
public const string MessageId = "SSS-MessageId";
1010
public const string Location = "Location";
11-
11+
1212
public static class ContentTypes
1313
{
1414
public const string Json = "application/json";
@@ -43,7 +43,7 @@ public static class ReadDirection
4343
public const int Forwards = 1;
4444
public const int Backwards = -1;
4545
}
46-
46+
4747
public const int MaxCount = 20;
4848
}
4949
}

src/SqlStreamStore.HAL/HttpContextExtensions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ namespace SqlStreamStore.HAL
22
{
33
using System;
44
using System.IO;
5-
using System.Linq;
65
using System.Net.Http;
76
using System.Net.Http.Headers;
87
using System.Threading.Tasks;
@@ -46,8 +45,9 @@ public static void SetStandardCorsHeaders(this HttpContext context, params HttpM
4645
{
4746
if(allowedMethods?.Length > 0)
4847
{
49-
context.Response.Headers.Append("Access-Control-Allow-Methods",
50-
allowedMethods.Select(_ => _.Method).ToArray());
48+
context.Response.Headers.Append(
49+
"Access-Control-Allow-Methods",
50+
Array.ConvertAll(allowedMethods, _ => _.Method));
5151
}
5252

5353
context.Response.Headers.Append(

src/SqlStreamStore.HAL/ReadAllStreamMiddleware.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ private static MidFunc GetStream(AllStreamResource allStream) => async (context,
3232
{
3333
var options = new ReadAllStreamOperation(context.Request);
3434

35-
var response = await allStream.GetPage(options, context.RequestAborted);
35+
var response = await allStream.Get(options, context.RequestAborted);
3636

3737
using(new OptionalHeadRequestWrapper(context))
3838
{
@@ -42,7 +42,7 @@ private static MidFunc GetStream(AllStreamResource allStream) => async (context,
4242

4343
private static MidFunc GetStreamMessage(AllStreamMessageResource allStreamMessages) => async (context, next) =>
4444
{
45-
var response = await allStreamMessages.GetMessage(
45+
var response = await allStreamMessages.Get(
4646
new ReadAllStreamMessageOperation(context.Request),
4747
context.RequestAborted);
4848

src/SqlStreamStore.HAL/ReadStreamMiddleware.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ private static MidFunc GetStream(StreamResource streams) => async (context, next
3131
{
3232
var options = new ReadStreamOperation(context.Request);
3333

34-
var response = await streams.GetPage(options, context.RequestAborted);
34+
var response = await streams.Get(options, context.RequestAborted);
3535

3636
using(new OptionalHeadRequestWrapper(context))
3737
{
@@ -43,7 +43,7 @@ private static MidFunc GetStreamMessage(StreamMessageResource streamMessages) =>
4343
{
4444
var options = new ReadStreamMessageByStreamVersionOperation(context.Request);
4545

46-
var response = await streamMessages.GetMessage(options, context.RequestAborted);
46+
var response = await streamMessages.Get(options, context.RequestAborted);
4747

4848
using(new OptionalHeadRequestWrapper(context))
4949
{

0 commit comments

Comments
 (0)