aboutsummaryrefslogtreecommitdiff
path: root/Timeline.Tests/Helpers/ResponseAssertions.cs
blob: 301ceef671bdad373a8041a919337293018b8129 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
using FluentAssertions;
using FluentAssertions.Execution;
using FluentAssertions.Formatting;
using FluentAssertions.Primitives;
using System;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Timeline.Models.Converters;
using Timeline.Models.Http;

namespace Timeline.Tests.Helpers
{
    public class HttpResponseMessageValueFormatter : IValueFormatter
    {
        public bool CanHandle(object value)
        {
            return value is HttpResponseMessage;
        }

        public string Format(object value, FormattingContext context, FormatChild formatChild)
        {
            string newline = context.UseLineBreaks ? Environment.NewLine : "";
            string padding = new string('\t', context.Depth);

            var res = (HttpResponseMessage)value;

            var builder = new StringBuilder();
            builder.Append($"{newline}{padding} Status Code: {res.StatusCode} ; Body: ");

            try
            {
                var body = res.Content.ReadAsStringAsync().Result;
                if (body.Length > 40)
                {
                    body = body[0..40] + " ...";
                }
                builder.Append(body);
            }
            catch (AggregateException)
            {
                builder.Append("NOT A STRING.");
            }

            return builder.ToString();
        }
    }

    public class HttpResponseMessageAssertions
            : ReferenceTypeAssertions<HttpResponseMessage, HttpResponseMessageAssertions>
    {
        static HttpResponseMessageAssertions()
        {
            Formatter.AddFormatter(new HttpResponseMessageValueFormatter());
        }

        public HttpResponseMessageAssertions(HttpResponseMessage instance)
        {
            Subject = instance;
        }

        protected override string Identifier => "HttpResponseMessage";

        public AndConstraint<HttpResponseMessageAssertions> HaveStatusCode(int expected, string because = "", params object[] becauseArgs)
        {
            return HaveStatusCode((HttpStatusCode)expected, because, becauseArgs);
        }

        public AndConstraint<HttpResponseMessageAssertions> HaveStatusCode(HttpStatusCode expected, string because = "", params object[] becauseArgs)
        {
            Execute.Assertion.BecauseOf(because, becauseArgs)
                .ForCondition(Subject.StatusCode == expected)
                .FailWith("Expected status code of {context:HttpResponseMessage} to be {0}{reason}, but found {1}.", expected, Subject.StatusCode);
            return new AndConstraint<HttpResponseMessageAssertions>(this);
        }

        public AndWhichConstraint<HttpResponseMessageAssertions, T> HaveJsonBody<T>(string because = "", params object[] becauseArgs)
        {
            var a = Execute.Assertion.BecauseOf(because, becauseArgs);
            string body;
            try
            {
                body = Subject.Content.ReadAsStringAsync().Result;
            }
            catch (AggregateException e)
            {
                a.FailWith("Expected response body of {context:HttpResponseMessage} to be json string{reason}, but failed to read it or it was not a string. Exception is {0}.", e.InnerExceptions);
                return new AndWhichConstraint<HttpResponseMessageAssertions, T>(this, null);
            }


            try
            {
                var options = new JsonSerializerOptions
                {
                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
                };
                options.Converters.Add(new JsonStringEnumConverter());
                options.Converters.Add(new JsonDateTimeConverter());

                var result = JsonSerializer.Deserialize<T>(body, options);

                return new AndWhichConstraint<HttpResponseMessageAssertions, T>(this, result);
            }
            catch (JsonException e)
            {
                a.FailWith("Expected response body of {context:HttpResponseMessage} to be json string{reason}, but failed to deserialize it. Exception is {0}.", e);
                return new AndWhichConstraint<HttpResponseMessageAssertions, T>(this, null);
            }
        }
    }

    public static class AssertionResponseExtensions
    {
        public static HttpResponseMessageAssertions Should(this HttpResponseMessage instance)
        {
            return new HttpResponseMessageAssertions(instance);
        }

        public static AndWhichConstraint<HttpResponseMessageAssertions, CommonResponse> HaveCommonBody(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
        {
            return assertions.HaveJsonBody<CommonResponse>(because, becauseArgs);
        }

        public static AndWhichConstraint<HttpResponseMessageAssertions, CommonDataResponse<TData>> HaveCommonDataBody<TData>(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
        {
            return assertions.HaveJsonBody<CommonDataResponse<TData>>(because, becauseArgs);
        }

        public static void BePut(this HttpResponseMessageAssertions assertions, bool create, string because = "", params object[] becauseArgs)
        {
            var body = assertions.HaveStatusCode(create ? 201 : 200, because, becauseArgs)
                .And.HaveJsonBody<CommonPutResponse>(because, becauseArgs)
                .Which;
            body.Code.Should().Be(0);
            body.Data.Create.Should().Be(create);
        }

        public static void BeDelete(this HttpResponseMessageAssertions assertions, bool delete, string because = "", params object[] becauseArgs)
        {
            var body = assertions.HaveStatusCode(200, because, becauseArgs)
                .And.HaveJsonBody<CommonDeleteResponse>(because, becauseArgs)
                .Which;
            body.Code.Should().Be(0);
            body.Data.Delete.Should().Be(delete);
        }

        public static void BeInvalidModel(this HttpResponseMessageAssertions assertions, string message = null)
        {
            message = string.IsNullOrEmpty(message) ? "" : ", " + message;
            assertions.HaveStatusCode(400, "Invalid Model Error must have 400 status code{0}", message)
                .And.HaveCommonBody("Invalid Model Error must have CommonResponse body{0}", message)
                .Which.Code.Should().Be(ErrorCodes.Http.Common.InvalidModel,
                "Invalid Model Error must have code {0} in body{1}",
                ErrorCodes.Http.Common.InvalidModel, message);
        }
    }
}