| | | 1 | | namespace ZhEaIsNsAaBn.IntegrationTestingBases; |
| | | 2 | | |
| | | 3 | | /// <summary> |
| | | 4 | | /// Base class for integration tests: gives access to the shared <see cref="SqlServerTestFixture{TProgram, TDbContext}"/ |
| | | 5 | | /// its <see cref="Client"/> and database, plus HTTP helpers and assertions. |
| | | 6 | | /// </summary> |
| | | 7 | | /// <typeparam name="TProgram">The <c>Program</c> class of the web application under test.</typeparam> |
| | | 8 | | /// <typeparam name="TDbContext">The app's <see cref="DbContext"/>.</typeparam> |
| | | 9 | | public abstract class IntegrationTestBase< TProgram, TDbContext > |
| | | 10 | | where TProgram : class |
| | | 11 | | where TDbContext : DbContext |
| | | 12 | | { |
| | | 13 | | internal const string NoFixtureMessage = "No SqlServerTestFixture is running. Add a [SetUpFixture] class that derive |
| | | 14 | | |
| | | 15 | | private SqlServerTestFixture<TProgram, TDbContext>? _fixture; |
| | | 16 | | |
| | | 17 | | |
| | | 18 | | /// <summary> |
| | | 19 | | /// The fixture this test uses: the one passed to <see cref="SetFixture"/> if any, otherwise the running |
| | | 20 | | /// <see cref="SqlServerTestFixture{TProgram, TDbContext}.Current"/>. |
| | | 21 | | /// </summary> |
| | | 22 | | /// <exception cref="InvalidOperationException">No fixture was set and none is running.</exception> |
| | | 23 | | protected SqlServerTestFixture<TProgram, TDbContext> Fixture |
| | 51 | 24 | | => _fixture ?? SqlServerTestFixture<TProgram, TDbContext>.Current ?? throw new InvalidOperationException( NoFixt |
| | | 25 | | |
| | | 26 | | /// <summary>The HTTP client for the app under test.</summary> |
| | | 27 | | /// <exception cref="InvalidOperationException">The fixture has not started yet.</exception> |
| | 12 | 28 | | protected HttpClient Client => Fixture.Client ?? throw new InvalidOperationException( "Client is not initialized" ); |
| | | 29 | | |
| | | 30 | | |
| | | 31 | | /// <summary> |
| | | 32 | | /// Uses <paramref name="fixture"/> instead of <see cref="SqlServerTestFixture{TProgram, TDbContext}.Current"/>. Onl |
| | | 33 | | /// when several fixtures run for the same app and context (e.g. two databases). Pass an instance NUnit started; a f |
| | | 34 | | /// created with <c>new</c> is never started. |
| | | 35 | | /// </summary> |
| | | 36 | | /// <param name="fixture">The fixture to use.</param> |
| | 3 | 37 | | protected void SetFixture( SqlServerTestFixture<TProgram, TDbContext> fixture ) => _fixture = fixture; |
| | | 38 | | |
| | | 39 | | /// <summary>Creates a new <typeparamref name="TDbContext"/> for the test database. The caller disposes it.</summary |
| | | 40 | | /// <returns>A new context.</returns> |
| | | 41 | | /// <exception cref="InvalidOperationException">The fixture has not started yet.</exception> |
| | 12 | 42 | | protected TDbContext GetDbContext() => Fixture.GetDbContext(); |
| | | 43 | | |
| | | 44 | | /// <summary>Deletes all test data (except ignored tables).</summary> |
| | | 45 | | /// <param name="cancellationToken">Token to cancel the operation.</param> |
| | | 46 | | /// <returns>A task that completes when the database is clean.</returns> |
| | | 47 | | /// <exception cref="InvalidOperationException">The fixture has not started yet.</exception> |
| | 15 | 48 | | protected Task ResetDatabaseAsync( CancellationToken cancellationToken = default ) => Fixture.ResetDatabaseAsync( ca |
| | | 49 | | |
| | | 50 | | /// <summary>Serializes <paramref name="obj"/> to camelCase JSON content.</summary> |
| | | 51 | | /// <typeparam name="T">The value type.</typeparam> |
| | | 52 | | /// <param name="obj">The value to send.</param> |
| | | 53 | | /// <returns><c>application/json</c> content in UTF-8.</returns> |
| | 3 | 54 | | protected static StringContent ToJsonContent< T >( T obj ) => HttpTestUtilities.ToJsonContent( obj ); |
| | | 55 | | |
| | | 56 | | /// <summary>Reads and deserializes a JSON response.</summary> |
| | | 57 | | /// <typeparam name="T">The expected body type.</typeparam> |
| | | 58 | | /// <param name="response">The response to read.</param> |
| | | 59 | | /// <param name="cancellationToken">Token to cancel the operation.</param> |
| | | 60 | | /// <returns>The deserialized body.</returns> |
| | | 61 | | /// <exception cref="InvalidOperationException">The body is empty, <c>null</c> or not valid JSON for <typeparamref n |
| | | 62 | | protected static Task<T> ReadResponseAsync< T >( HttpResponseMessage response, CancellationToken cancellationToken = |
| | 6 | 63 | | => HttpTestUtilities.ReadResponseAsync<T>( response, cancellationToken ); |
| | | 64 | | |
| | | 65 | | /// <summary>Reads the response body as a string.</summary> |
| | | 66 | | /// <param name="response">The response to read.</param> |
| | | 67 | | /// <param name="cancellationToken">Token to cancel the operation.</param> |
| | | 68 | | /// <returns>The body.</returns> |
| | | 69 | | protected static Task<string> ReadResponseAsStringAsync( HttpResponseMessage response, CancellationToken cancellatio |
| | 0 | 70 | | => HttpTestUtilities.ReadResponseAsStringAsync( response, cancellationToken ); |
| | | 71 | | |
| | | 72 | | /// <summary>Creates empty multipart form-data content for uploads.</summary> |
| | | 73 | | /// <returns>New multipart content. The caller disposes it.</returns> |
| | 0 | 74 | | protected static MultipartFormDataContent CreateMultipartContent() => HttpTestUtilities.CreateMultipartContent(); |
| | | 75 | | |
| | | 76 | | /// <summary>Adds a file (<c>application/octet-stream</c>) to multipart content.</summary> |
| | | 77 | | /// <param name="content">The multipart content.</param> |
| | | 78 | | /// <param name="fileBytes">The file bytes.</param> |
| | | 79 | | /// <param name="fileName">The file name sent to the server.</param> |
| | | 80 | | /// <param name="fieldName">The form field name. Default: <c>file</c>.</param> |
| | | 81 | | protected static void AddFileToMultipart( MultipartFormDataContent content, byte[] fileBytes, string fileName, strin |
| | 0 | 82 | | => HttpTestUtilities.AddFileToMultipart( content, fileBytes, fileName, fieldName ); |
| | | 83 | | |
| | | 84 | | /// <summary>Adds a text field to multipart content.</summary> |
| | | 85 | | /// <param name="content">The multipart content.</param> |
| | | 86 | | /// <param name="name">The field name.</param> |
| | | 87 | | /// <param name="value">The field value.</param> |
| | | 88 | | protected static void AddFormFieldToMultipart( MultipartFormDataContent content, string name, string value ) |
| | 0 | 89 | | => HttpTestUtilities.AddFormFieldToMultipart( content, name, value ); |
| | | 90 | | |
| | | 91 | | /// <summary>Asserts a 2xx status code.</summary> |
| | | 92 | | /// <param name="response">The response to check.</param> |
| | | 93 | | protected static void AssertSuccessResponse( HttpResponseMessage response ) |
| | 3 | 94 | | => Assert.That( response.IsSuccessStatusCode, Is.True, $"Expected successful response but got {response.StatusCo |
| | | 95 | | |
| | | 96 | | /// <summary>Asserts a specific status code.</summary> |
| | | 97 | | /// <param name="response">The response to check.</param> |
| | | 98 | | /// <param name="expectedStatusCode">The expected status code.</param> |
| | | 99 | | protected static void AssertStatusCode( HttpResponseMessage response, HttpStatusCode expectedStatusCode ) |
| | 3 | 100 | | => Assert.That( response.StatusCode, Is.EqualTo( expectedStatusCode ), $"Expected {expectedStatusCode} but got { |
| | | 101 | | |
| | | 102 | | /// <summary>Asserts the response media type, e.g. <c>application/json</c>.</summary> |
| | | 103 | | /// <param name="response">The response to check.</param> |
| | | 104 | | /// <param name="expectedContentType">The expected media type, without parameters such as <c>charset</c>.</param> |
| | | 105 | | protected static void AssertContentType( HttpResponseMessage response, string expectedContentType ) |
| | | 106 | | { |
| | 3 | 107 | | var actualContentType = response.Content.Headers.ContentType?.MediaType; |
| | | 108 | | |
| | | 109 | | |
| | 3 | 110 | | Assert.That( actualContentType, Is.EqualTo( expectedContentType ), $"Expected content type {expectedContentType} |
| | 3 | 111 | | } |
| | | 112 | | } |