<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ayman</title>
    <description>The latest articles on DEV Community by Ayman (@deman404).</description>
    <link>https://dev.to/deman404</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2068770%2Fd507f07d-c6e6-4d39-8b56-6733c5bc2e4c.png</url>
      <title>DEV Community: Ayman</title>
      <link>https://dev.to/deman404</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/deman404"/>
    <language>en</language>
    <item>
      <title>Building a WPF API Client: HttpClient, Local Storage, and Lessons from QA</title>
      <dc:creator>Ayman</dc:creator>
      <pubDate>Wed, 28 Oct 2026 15:15:28 +0000</pubDate>
      <link>https://dev.to/deman404/building-a-wpf-api-client-httpclient-local-storage-and-lessons-from-qa-1294</link>
      <guid>https://dev.to/deman404/building-a-wpf-api-client-httpclient-local-storage-and-lessons-from-qa-1294</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb1u9bg2s4gqt8vdyb9yd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb1u9bg2s4gqt8vdyb9yd.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
NorthAPI is a local-first REST API client for Windows, built with C#, .NET 10, and WPF. It sends HTTP requests, displays responses, saves collections and history locally, and generates C# and cURL examples.&lt;/p&gt;

&lt;p&gt;Its latest documented release QA run finished with &lt;strong&gt;61 tests executed, 60 passed, and one failed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The failure was useful: a generated cURL request looked correct, but its Unicode body differed from the original when executed through the tested native Windows cURL. Compiling the C# examples had not established that both generators reproduced the same request.&lt;/p&gt;

&lt;p&gt;This article walks through the implementation and the checks that exposed those gaps. NorthAPI is still a preview; the release recommendation remains &lt;strong&gt;BLOCKED&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flbrz1bc1tzxnfoytgtd4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flbrz1bc1tzxnfoytgtd4.png" alt="NorthAPI workspace with saved requests, environment selection, and a JSON response" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Actual application capture using an isolated local demo server and synthetic data.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Separate the editor from the HTTP engine
&lt;/h2&gt;

&lt;p&gt;The current UI uses reusable WPF &lt;code&gt;UserControl&lt;/code&gt;s with code-behind for interactions. It is not a full MVVM sample. The useful separation is between controls and the services that build, send, store, and export requests.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RequestEditor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Capture settings, handle Send/Cancel, and present results.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;BodyEditor&lt;/code&gt; / &lt;code&gt;AuthorizationEditor&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Edit and restore their sections of a request.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RequestDefinition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Serializable settings, including unresolved environment references.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RequestBuilder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Resolve a copy, construct the URI and body, and apply headers/auth.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HttpRequestEngine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Execute HTTP, enforce a deadline, and read a bounded preview.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResponseViewer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Render status, timing, size, body, and headers.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorkspaceStore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Persist collections, environments, and history.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CodeGenerator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Produce examples from the request definition used by Send.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The request path is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WPF editor
  -&amp;gt; RequestDefinition snapshot
  -&amp;gt; resolve variables in a copy
  -&amp;gt; build HttpRequestMessage
  -&amp;gt; HttpRequestEngine.SendAsync
  -&amp;gt; HttpResponseResult
  -&amp;gt; WPF response viewer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Taking a snapshot before sending gives the operation a stable set of settings. Resolving a deep copy preserves saved templates such as &lt;code&gt;{{baseUrl}}/users&lt;/code&gt; instead of replacing them with one environment's URL.&lt;/p&gt;

&lt;p&gt;The response is a separate result object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;HttpResponseResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ReasonPhrase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;MediaType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;BodyBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;IsTruncated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TimeSpan&lt;/span&gt; &lt;span class="n"&gt;Elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;HttpResponseHeader&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;Cookies&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can represent a completed HTTP exchange whose status happens to be &lt;code&gt;401&lt;/code&gt; or &lt;code&gt;500&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Reuse HttpClient and own the operation's deadline
&lt;/h2&gt;

&lt;p&gt;The engine's default transport is a shared client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;SharedClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;SocketsHttpHandler&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;AutomaticDecompression&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;DecompressionMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GZip&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;DecompressionMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deflate&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;DecompressionMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Brotli&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;AllowAutoRedirect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;UseCookies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;PooledConnectionLifetime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InfiniteTimeSpan&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids constructing and disposing a connection pool for every click. The pooled connection lifetime also gives connections a finite lifetime. Five minutes is a project setting, not a universal networking recommendation. Microsoft's &lt;a href="https://learn.microsoft.com/dotnet/fundamentals/networking/httpclient-guidelines" rel="noopener noreferrer"&gt;HttpClient lifetime guidance&lt;/a&gt; explains the connection-pooling and DNS tradeoffs.&lt;/p&gt;

&lt;p&gt;Automatic cookie handling is disabled. The UI can inspect response &lt;code&gt;Set-Cookie&lt;/code&gt; values, but the application does not maintain a browser-style cookie jar.&lt;/p&gt;

&lt;p&gt;The engine also accepts an injected &lt;code&gt;HttpClient&lt;/code&gt;. Tests use this to isolate DNS failure from proxy behavior without changing the application's default transport.&lt;/p&gt;

&lt;p&gt;Each send has a linked cancellation source. These are excerpts from the engine; validation and preview reading are separate parts of the method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;deadline&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;CancellationTokenSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateLinkedTokenSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CancelAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;HttpCompletionOption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseHeadersRead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsStreamAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ResponseHeadersRead&lt;/code&gt; lets the engine read the body itself, but it creates a timeout boundary that is easy to overlook. Once that &lt;code&gt;SendAsync&lt;/code&gt; completes, a client-level timeout does not independently supervise later body reads. The content operations need their own deadline handling. This is documented in &lt;a href="https://learn.microsoft.com/dotnet/api/system.net.http.httpcompletionoption?view=net-10.0" rel="noopener noreferrer"&gt;HttpCompletionOption&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;NorthAPI passes the same deadline token through stream reads. Its tests cover a server that delays headers and one that sends headers promptly but delays the body.&lt;/p&gt;

&lt;p&gt;Caller cancellation and deadline expiration are distinguished:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OperationCanceledException&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="n"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TimeoutException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;$"Request timed out after &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; seconds."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A user clicking Cancel gets a cancellation result. An expired deadline gets a timeout result.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Test the dispatcher, not just the asynchronous method
&lt;/h2&gt;

&lt;p&gt;The request editor awaits the engine from its WPF event handler. The engine's internal awaits use &lt;code&gt;ConfigureAwait(false)&lt;/code&gt;; the editor's await resumes through its captured WPF context and updates controls there.&lt;/p&gt;

&lt;p&gt;During a request, the editor holds a &lt;code&gt;CancellationTokenSource&lt;/code&gt; and changes Send to Cancel. Clicking it again cancels that source. Closing a request tab also cancels an active operation and releases the editor's store subscription.&lt;/p&gt;

&lt;p&gt;The UI test checks more than eventual completion. It runs a &lt;code&gt;DispatcherTimer&lt;/code&gt; while a slow local request is pending, confirms the timer keeps ticking, and verifies that cancellation or timeout restores Send and displays an understandable error.&lt;/p&gt;

&lt;p&gt;This establishes responsiveness during those network waits. It does not establish that every large JSON document formats without a pause: parsing and rendering still have CPU and allocation costs on the UI side.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Keep HTTP failures inspectable
&lt;/h2&gt;

&lt;p&gt;An API client needs the body of a &lt;code&gt;400&lt;/code&gt; just as much as the body of a &lt;code&gt;200&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The engine reads the response without calling &lt;code&gt;EnsureSuccessStatusCode()&lt;/code&gt;. Status, headers, and body are returned together. The viewer can show an error-colored status badge while keeping the response available for inspection.&lt;/p&gt;

&lt;p&gt;Transport failures take a different path: invalid settings, DNS resolution, connection refusal, TLS validation, cancellation, and timeout receive separate error messages.&lt;/p&gt;

&lt;p&gt;The deterministic integration suite exercises:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Theory&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;InlineData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;InlineData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;InlineData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;404&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;InlineData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;429&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;InlineData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;InlineData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;503&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Non_success_responses_remain_inspectable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;QaServer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpRequestMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Url&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/status/"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpRequestEngine&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RootElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetInt32&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;True&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BodyBytes&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Elapsed&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;QaServer&lt;/code&gt; is the repository's loopback fixture. Companion WPF tests render these results so a successful engine assertion does not conceal a broken error-state UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Give response size and time precise meanings
&lt;/h2&gt;

&lt;p&gt;The preview is capped at &lt;strong&gt;8 MiB&lt;/strong&gt;. The engine reads at most one additional byte to determine whether it is truncated, then retains the bounded preview.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Size&lt;/strong&gt; counts retained body bytes after transport decompression. It is not the length of the formatted JSON string or the compressed transfer size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time&lt;/strong&gt; measures the operation through reading the preview. For a truncated response, it is not the time required to download the complete original body.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The viewer displays truncation, including what Copy and Save will contain. It supports Pretty/Raw views, JSON token colors, line numbers, and header filtering. Invalid JSON falls back to text; an empty response gets an explicit empty-body display.&lt;/p&gt;

&lt;p&gt;Syntax highlighting is limited to 256 KiB of displayed text. Larger previews remain readable without token coloring. A preview cap limits input size, but it does not make parsing, formatted text, or WPF document objects free.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fynxebc1ahbc7ej0a6clr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fynxebc1ahbc7ej0a6clr.png" alt="NorthAPI raw JSON request editor alongside a successful response" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The body editor supports raw formats, text form-data, URL-encoded forms, binary files, and GraphQL query/variables input.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Preserve templates and reject unresolved variables
&lt;/h2&gt;

&lt;p&gt;Environment values can appear in URLs, parameters, headers, applicable auth fields, and request bodies.&lt;/p&gt;

&lt;p&gt;The resolver builds a dictionary from enabled variables and resolves references recursively. It tracks visited names and rejects missing variables, cycles, and excessive nesting.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;baseUrl = https://api.example.com
usersUrl = {{baseUrl}}/users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A request can use &lt;code&gt;{{usersUrl}}&lt;/code&gt;, while the saved definition retains the reference. These settings would be rejected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a = {{b}}
b = {{a}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engine should not discover a missing variable by attempting DNS resolution for an unresolved placeholder. Resolution and validation happen before sending.&lt;/p&gt;

&lt;p&gt;Substitution is textual. It is not a JSON templating language that automatically escapes each inserted value for its surrounding context. Values inserted into a raw JSON body still need to produce the JSON the API expects.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Local-first storage still needs secret protection
&lt;/h2&gt;

&lt;p&gt;One concrete QA finding was plaintext workspace persistence.&lt;/p&gt;

&lt;p&gt;Protecting only a password property would miss several other locations: a custom API-key header, a token in an environment, credentials in a URL, or a sensitive body preserved in history.&lt;/p&gt;

&lt;p&gt;The revised store protects the &lt;strong&gt;entire serialized workspace payload&lt;/strong&gt; with Windows DPAPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ProtectedData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Protect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;optionalEntropy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DataProtectionScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ciphertext is Base64-encoded into a JSON envelope. DPAPI supplies the protection; Base64 packages the encrypted bytes for storage.&lt;/p&gt;

&lt;p&gt;The write path serializes a snapshot, protects it, writes a temporary sibling file, flushes it, and replaces the destination. An in-process lock serializes store updates. This is a local file store, not a multi-process database or a synchronization engine.&lt;/p&gt;

&lt;p&gt;Migration and recovery need the same attention as normal saves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Valid legacy plaintext files migrate to protected storage.&lt;/li&gt;
&lt;li&gt;Old application recovery backups are protected too.&lt;/li&gt;
&lt;li&gt;Corrupt or undecryptable state is preserved, and saving is disabled until recovery.&lt;/li&gt;
&lt;li&gt;Tests check that synthetic secrets do not remain in plaintext storage artifacts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;CurrentUser&lt;/code&gt; ties protection to the Windows user context. It does not provide a boundary against malicious code running as that user, and a protected workspace file is not a portable backup for a different account. Microsoft's &lt;a href="https://learn.microsoft.com/dotnet/standard/security/how-to-use-data-protection" rel="noopener noreferrer"&gt;DPAPI documentation&lt;/a&gt; describes the platform and scope model.&lt;/p&gt;

&lt;p&gt;Explicit exports are separate. Collection exports and generated code retain the values the user requested, including credentials when present. Quietly removing those values would change the request being exported. Those outputs need to be treated as sensitive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy7natqdelwvs1xf8918x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy7natqdelwvs1xf8918x.png" alt="NorthAPI authorization editor with a demo username and a masked demo password" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Screenshot credentials are synthetic. The editor supports Basic, Bearer, API Key, and manually supplied OAuth 2.0 access tokens; token acquisition and refresh are not implemented.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Keep diagnostics useful without echoing credentials
&lt;/h2&gt;

&lt;p&gt;Raw exception messages can carry values from invalid request settings. Persisting those messages in history creates another potential disclosure path.&lt;/p&gt;

&lt;p&gt;NorthAPI maps failures to fixed categories. For example, these arms belong to the &lt;code&gt;SafeDiagnostics.RequestError&lt;/code&gt; switch expression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;HttpRequestException&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;HttpRequestError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpRequestError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SecureConnectionError&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"The TLS certificate could not be validated or the secure connection failed."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="n"&gt;HttpRequestException&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;HttpRequestError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpRequestError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NameResolutionError&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"The server name could not be resolved. Check the URL and DNS settings."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI and persisted error summaries use that mapping instead of inserting raw exception text.&lt;/p&gt;

&lt;p&gt;Security tests use synthetic canaries, capture Console and Trace during relevant store/export/code-generation operations, and assert that credentials are not emitted. Sensitive comparisons use boolean assertions so a failure does not print the expected and actual secret.&lt;/p&gt;

&lt;p&gt;That is a defined scope, not a claim about every possible debugger, operating-system dump, or externally enabled tracing tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Execute generated examples and compare what the server receives
&lt;/h2&gt;

&lt;p&gt;Sending and code generation start from &lt;code&gt;RequestDefinition&lt;/code&gt; and the selected environment. Sharing that model reduces drift, but cannot prove two clients send identical requests.&lt;/p&gt;

&lt;p&gt;The code-generation checks therefore:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send the original through NorthAPI.&lt;/li&gt;
&lt;li&gt;Generate C# and cURL examples.&lt;/li&gt;
&lt;li&gt;Compile and execute the C# example.&lt;/li&gt;
&lt;li&gt;Execute native cURL with the generated argument stream.&lt;/li&gt;
&lt;li&gt;Compare the server's observed method, target URL, application headers, and body.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;GET, POST JSON, custom-header, Basic, and Bearer cases matched.&lt;/p&gt;

&lt;p&gt;An additional Unicode case used this non-secret body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"café"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The C# substep compiled, executed, and matched. The tested native Windows cURL 7.79.1 execution produced a different body, so the combined test failed.&lt;/p&gt;

&lt;p&gt;The generator targets a POSIX shell. The recorded native-Windows test used a test-only POSIX literal quote decoder to produce arguments for the real &lt;code&gt;curl.exe&lt;/code&gt;; it did not establish end-to-end copy/paste behavior in bash or sh. The Windows argv/code-page versus intended-shell cause was not fully isolated.&lt;/p&gt;

&lt;p&gt;The report keeps both facts visible: a real native cURL execution failed its equivalence check, and full POSIX-shell validation remains pending. Removing Unicode from the fixture or rewriting the expected body would hide the finding.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Use deterministic fixtures and keep release claims separate
&lt;/h2&gt;

&lt;p&gt;The local server supplies controlled status codes, malformed JSON, empty responses, delayed headers, and delayed bodies. A local TLS fixture presents an untrusted certificate; the client rejects it without disabling validation.&lt;/p&gt;

&lt;p&gt;External HTTPS tests also exercise GET, POST, PUT, PATCH, and DELETE against httpbingo's documented method endpoints with synthetic payloads and an explicit QA User-Agent.&lt;/p&gt;

&lt;p&gt;Earlier attempts returned &lt;code&gt;402&lt;/code&gt;. A later setup using documented method endpoints and an explicit User-Agent passed. Because both route and User-Agent changed, the exact cause of the earlier result was not isolated. The failed evidence was retained.&lt;/p&gt;

&lt;p&gt;For persistence, UI Automation launches the actual app, sends and saves a request, closes it, and starts another process. The restarted app displays the saved collection, history, and environment. Temporary data directories keep fixtures away from the user's workspace.&lt;/p&gt;

&lt;p&gt;The latest documented evidence is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Recorded outcome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Discoverable release suite&lt;/td&gt;
&lt;td&gt;61 executed: 60 passed, 1 failed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Existing HTTP/workspace console checks&lt;/td&gt;
&lt;td&gt;62 passed, counted separately.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Existing WPF workflow console checks&lt;/td&gt;
&lt;td&gt;16 passed, counted separately.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-contained win-x64 publish&lt;/td&gt;
&lt;td&gt;Succeeded.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Published executable smoke test on development PC&lt;/td&gt;
&lt;td&gt;Passed startup, HTTP, save, shutdown, and restart checks.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Separate clean Windows PC without .NET&lt;/td&gt;
&lt;td&gt;Blocked: no such environment was available for the recorded run.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native Windows cURL Unicode equivalence&lt;/td&gt;
&lt;td&gt;Failed; remains open.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;End-to-end POSIX-shell execution&lt;/td&gt;
&lt;td&gt;Not established by the recorded tests.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run the discoverable suite from the repository root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;NorthAPI.slnx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"trx;LogFileName=release-qa.trx"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--results-directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;artifacts/qa&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;IntermediateOutputPath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;obj/qa10/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The known cURL regression makes that suite exit nonzero. These are recorded results, not a fresh test run performed while writing this article.&lt;/p&gt;

&lt;p&gt;A self-contained build can be created with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;northApi.csproj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--runtime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;win-x64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--self-contained&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;artifacts/win-x64&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entire output directory is required. A successful publish and a development-PC smoke test do not prove that startup, permissions, networking, and shutdown work on a separate machine without .NET installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this project has made clearer
&lt;/h2&gt;

&lt;p&gt;The most useful boundaries have been concrete: a serializable request snapshot, a transport service with an operation-wide deadline, a response result that retains HTTP failures, and a store that protects all of its sensitive payload.&lt;/p&gt;

&lt;p&gt;The most useful QA checks crossed those boundaries. They kept a dispatcher ticking during a timeout, restarted the actual process, decrypted and reloaded protected state, and compared generated clients with the request the server received.&lt;/p&gt;

&lt;p&gt;NorthAPI's original project code is MIT-licensed. Imported artwork has separate provenance notes in the repository. The preview still has known release blockers, and the QA report is part of the project rather than a claim that every box is green.&lt;/p&gt;

&lt;p&gt;Source and setup: &lt;a href="https://github.com/deman404/NorthAPI" rel="noopener noreferrer"&gt;deman404/NorthAPI&lt;/a&gt;.&lt;br&gt;
Implementation details and execution evidence: &lt;a href="https://github.com/deman404/NorthAPI/blob/main/RELEASE_QA.md" rel="noopener noreferrer"&gt;RELEASE_QA.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you work on desktop developer tools, what would you test next: authentication boundaries, streamed responses, persistence failures, or generated-client compatibility? A focused bug report with a synthetic reproduction would be particularly useful.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
