Back to Repositories

Testing Legacy API Message Delivery Status Validation in Postal

This test suite validates the delivery status endpoints in Postal’s legacy messages API, focusing on authentication, error handling, and delivery status retrieval functionality.

Test Coverage Overview

The test suite provides comprehensive coverage of the /api/v1/messages/deliveries endpoint, examining authentication flows and delivery status retrieval.

  • Authentication validation for API access
  • Server suspension checks
  • Message ID validation
  • Delivery status response formatting

Implementation Analysis

The tests utilize RSpec’s request specs to simulate HTTP interactions with the API endpoint. The implementation follows a context-driven approach, systematically testing different authentication scenarios and response conditions.

  • Request-based testing with headers and params
  • JSON response validation
  • Factory-based test data generation

Technical Details

The test suite leverages several technical components:

  • RSpec request testing framework
  • Factory-based fixtures
  • JSON response parsing
  • Custom MessageFactory for test data
  • Header-based API authentication

Best Practices Demonstrated

The test suite exemplifies several testing best practices in API validation:

  • Comprehensive error case coverage
  • Isolated test contexts
  • Clear test descriptions
  • Consistent response validation
  • Proper test data setup and teardown

postalserver/postal

spec/apis/legacy_api/messages/deliveries_spec.rb

            
# frozen_string_literal: true

require "rails_helper"

RSpec.describe "Legacy Messages API", type: :request do
  describe "/api/v1/messages/deliveries" do
    context "when no authentication is provided" do
      it "returns an error" do
        post "/api/v1/messages/deliveries"
        expect(response.status).to eq 200
        parsed_body = JSON.parse(response.body)
        expect(parsed_body["status"]).to eq "error"
        expect(parsed_body["data"]["code"]).to eq "AccessDenied"
      end
    end

    context "when the credential does not match anything" do
      it "returns an error" do
        post "/api/v1/messages/deliveries", headers: { "x-server-api-key" => "invalid" }
        expect(response.status).to eq 200
        parsed_body = JSON.parse(response.body)
        expect(parsed_body["status"]).to eq "error"
        expect(parsed_body["data"]["code"]).to eq "InvalidServerAPIKey"
      end
    end

    context "when the credential belongs to a suspended server" do
      it "returns an error" do
        server = create(:server, :suspended)
        credential = create(:credential, server: server)
        post "/api/v1/messages/deliveries", headers: { "x-server-api-key" => credential.key }
        expect(response.status).to eq 200
        parsed_body = JSON.parse(response.body)
        expect(parsed_body["status"]).to eq "error"
        expect(parsed_body["data"]["code"]).to eq "ServerSuspended"
      end
    end

    context "when the credential is valid" do
      let(:server) { create(:server) }
      let(:credential) { create(:credential, server: server) }

      context "when no message ID is provided" do
        it "returns an error" do
          post "/api/v1/messages/deliveries", headers: { "x-server-api-key" => credential.key }
          expect(response.status).to eq 200
          parsed_body = JSON.parse(response.body)
          expect(parsed_body["status"]).to eq "parameter-error"
          expect(parsed_body["data"]["message"]).to match(/`id` parameter is required but is missing/)
        end
      end

      context "when the message ID does not exist" do
        it "returns an error" do
          post "/api/v1/messages/deliveries",
               headers: { "x-server-api-key" => credential.key,
                          "content-type" => "application/json" },
               params: { id: 123 }.to_json
          expect(response.status).to eq 200
          parsed_body = JSON.parse(response.body)
          expect(parsed_body["status"]).to eq "error"
          expect(parsed_body["data"]["code"]).to eq "MessageNotFound"
          expect(parsed_body["data"]["id"]).to eq 123
        end
      end

      context "when the message ID exists" do
        let(:server) { create(:server) }
        let(:credential) { create(:credential, server: server) }
        let(:message) { MessageFactory.outgoing(server) }

        before do
          message.create_delivery("SoftFail", details: "no server found",
                                              output: "404",
                                              sent_with_ssl: true,
                                              log_id: "1234",
                                              time: 1.2)
          message.create_delivery("Sent", details: "sent successfully",
                                          output: "200",
                                          sent_with_ssl: false,
                                          log_id: "5678",
                                          time: 2.2)
        end

        before do
          post "/api/v1/messages/deliveries",
               headers: { "x-server-api-key" => credential.key,
                          "content-type" => "application/json" },
               params: { id: message.id }.to_json
        end

        it "returns an array of deliveries" do
          expect(response.status).to eq 200
          parsed_body = JSON.parse(response.body)
          expect(parsed_body["status"]).to eq "success"
          expect(parsed_body["data"]).to match([
                                                 { "id" => kind_of(Integer),
                                                   "status" => "SoftFail",
                                                   "details" => "no server found",
                                                   "output" => "404",
                                                   "sent_with_ssl" => true,
                                                   "log_id" => "1234",
                                                   "time" => 1.2,
                                                   "timestamp" => kind_of(Float) },
                                                 { "id" => kind_of(Integer),
                                                   "status" => "Sent",
                                                   "details" => "sent successfully",
                                                   "output" => "200",
                                                   "sent_with_ssl" => false,
                                                   "log_id" => "5678",
                                                   "time" => 2.2,
                                                   "timestamp" => kind_of(Float) },
                                               ])
        end
      end
    end
  end
end