# frozen_string_literal: true require "swagger_helper" RSpec.describe "Customers", swagger_doc: "v1.yaml", feature: :api_v1 do let!(:enterprise1) { create(:enterprise, name: "The Farm") } let!(:enterprise2) { create(:enterprise) } let!(:enterprise3) { create(:enterprise) } let!(:customer1) { create( :customer, enterprise: enterprise1, created_manually: true, terms_and_conditions_accepted_at: Time.zone.parse("2000-01-01"), tag_list: ["long-term"], ship_address: create(:address), ) } let!(:customer2) { create(:customer, enterprise: enterprise1, created_manually: true,) } let!(:customer3) { create(:customer, enterprise: enterprise2, created_manually: true,) } let(:json_response_ids) { json_response[:data]&.pluck(:id) } let(:json_error_detail) { json_response[:errors][0][:detail] } before do login_as enterprise1.owner end path "/api/v1/customers" do get "List customers" do tags "Customers" parameter name: :enterprise_id, in: :query, type: :string parameter name: "extra_fields[customer]", in: :query, type: :string, example: :balance, description: "Add extra fields to each customer" produces "application/json" response "200", "Customers list" do let(:enterprise_id) { enterprise1.id } let("extra_fields[customer]") { :balance } schema '$ref': "#/components/schemas/customers_collection" run_test! end end describe "returning results based on permissions" do context "as guest user" do before { login_as nil } it "returns no customers" do get "/api/v1/customers" expect(json_response_ids).to eq [] end it "returns not even customers without user id" do customer3.update!(user_id: nil) get "/api/v1/customers" expect(json_response_ids).to eq [] end end context "as an enterprise owner" do before { login_as enterprise1.owner } it "returns customers of enterprises the user manages" do get "/api/v1/customers" expect(json_response_ids).to eq [customer1.id.to_s, customer2.id.to_s] end end context "as another enterprise owner" do before { login_as enterprise2.owner } it "returns customers of enterprises the user manages" do get "/api/v1/customers" expect(json_response_ids).to eq [customer3.id.to_s] end end context "as a user who manages the enterprise" do let!(:user){ enterprise3.users.first } before do EnterpriseRole.create!(user:, enterprise: enterprise1) login_as user end it "returns customers of enterprises the user manages" do get "/api/v1/customers" expect(json_response_ids).to eq [customer1.id.to_s, customer2.id.to_s] end end context "as an enterprise that has edit profile permission" do let!(:user){ enterprise3.users.first } before do EnterpriseRelationship.create!(parent: enterprise1, child: enterprise3, permissions_list: [:edit_profile]) login_as user end it "shoult not return customers of the managed enterprise" do get "/api/v1/customers" expect(json_response_ids).to eq [] end end context "with ransack params searching for specific customers" do before { login_as enterprise2.owner } it "does not show results the user doesn't have permissions to view" do get "/api/v1/customers", params: { q: { id_eq: customer2.id } } expect(json_response_ids).to eq [] end end end describe "pagination" do it "renders the first page" do get "/api/v1/customers", params: { page: "1" } expect(json_response_ids).to eq [customer1.id.to_s, customer2.id.to_s] end it "renders the second page" do get "/api/v1/customers", params: { page: "2", per_page: "1" } expect(json_response_ids).to eq [customer2.id.to_s] end it "renders beyond the available pages" do get "/api/v1/customers", params: { page: "2" } expect(json_response_ids).to eq [] end it "informs about invalid pages" do get "/api/v1/customers", params: { page: "0" } expect(json_response_ids).to eq nil expect(json_error_detail).to eq "expected :page >= 1; got 0" end end describe "query parameters" do describe "extra_fields[customer]" do context "with balance" do it "adds balance to each customer" do get "/api/v1/customers", params: { extra_fields: { customer: :balance } } balances = json_response[:data].map{ |c| c[:attributes][:balance] } expect(balances.all?{ |b| b.is_a? Numeric }).to eq(true) end end context "with unknown field" do it "returns unprocessable entity" do get "/api/v1/customers", params: { extra_fields: { customer: :unknown } } expect([response.status, json_error_detail]).to eq [422, "Unsupported fields: unknown"] end end context "when not recevied" do it "does not add balances" do get "/api/v1/customers" balances = json_response[:data].map{ |c| c[:attributes][:balance] } expect([response.status, balances.compact]).to eq [200, []] end end end end post "Create customer" do tags "Customers" consumes "application/json" produces "application/json" parameter name: :customer, in: :body, schema: { type: :object, properties: CustomerSchema.writable_attributes, required: CustomerSchema.required_attributes } response "201", "Minimal customer created" do let(:customer) do { email: "test@example.com", enterprise_id: enterprise1.id.to_s } end schema '$ref': "#/components/schemas/customer" run_test! do expect(json_response[:data][:attributes]).to include( allow_charges: false, terms_and_conditions_accepted_at: nil, ) customer = Customer.find(json_response[:data][:attributes][:id]) expect(customer.created_manually).to eq true end end response "201", "Example customer created" do let(:customer) do CustomerSchema.writable_attributes.transform_values do |attribute| attribute[:example] end.merge( enterprise_id: enterprise1.id, ) end schema '$ref': "#/components/schemas/customer" run_test! do expect(json_response[:data][:attributes]).to include( first_name: "Alice", last_name: "Springs", code: "BUYER1", email: "alice@example.com", enterprise_id: enterprise1.id, tags: ["staff", "discount"], shipping_address: CustomerSchema.address_example ) end end response "422", "Unpermitted parameter" do let(:customer) do { email: "test@example.com", enterprise_id: enterprise1.id.to_s, allow_charges: true, } end schema '$ref': "#/components/schemas/error_response" run_test! do expect(json_error_detail).to eq "Parameters not allowed in this request: allow_charges" end end response "422", "Unprocessable entity" do let(:customer) { {} } schema '$ref': "#/components/schemas/error_response" run_test! do expect(json_error_detail).to eq "A required parameter is missing or empty: customer" expect(json_response[:meta]).to eq nil end end end end path "/api/v1/customers/{id}" do get "Show customer" do tags "Customers" parameter name: :id, in: :path, type: :string produces "application/json" response "200", "Customer" do let(:id) { customer1.id } schema CustomerSchema.schema( require_all: true, extra_fields: { name: :balance, required: true } ) run_test! do date_time_string = json_response[:data][:attributes][:terms_and_conditions_accepted_at] expect(date_time_string).to match /^2000-01-01T00:00:00.000[Z+-].*$/ expect(DateTime.parse(date_time_string)).to eq( customer1.terms_and_conditions_accepted_at ) end end response "404", "Not found" do let(:id) { 0 } schema '$ref': "#/components/schemas/error_response" run_test! do expect(json_error_detail).to eq "The resource you were looking for could not be found." end end context "without authentication" do before { logout } response "401", "Unauthorized" do let(:id) { customer1.id } schema '$ref': "#/components/schemas/error_response" run_test! do expect(json_error_detail).to eq "You are not authorized to perform that action." end end end describe "related records" do it "contains exactly the defined relationships" do get "/api/v1/customers/#{customer1.id}" relationships = json_response[:data][:relationships].keys expect(relationships).to match_array CustomerSchema.relationships.map(&:to_s) end it "serializes the enterprise relationship" do expected_enterprise_data = { "data" => { "id" => customer1.enterprise_id.to_s, "type" => "enterprise" }, "links" => { "related" => "http://test.host/api/v1/enterprises/#{customer1.enterprise_id}" } } get "/api/v1/customers/#{customer1.id}" expect(json_response[:data][:relationships][:enterprise]).to eq(expected_enterprise_data) end end describe "including related records" do it "doesn't include other records by default" do get "/api/v1/customers/#{customer1.id}" expect(json_response[:included]).to eq nil end it "includes enterprise data on request" do get "/api/v1/customers/#{customer1.id}?include=enterprise" expect(json_response[:included].size).to eq 1 expect(json_response[:included].first).to include( id: enterprise1.id.to_s, type: "enterprise", attributes: { id: enterprise1.id, name: "The Farm", } ) end end end put "Update customer" do tags "Customers" parameter name: :id, in: :path, type: :string consumes "application/json" produces "application/json" parameter name: :customer, in: :body, schema: { type: :object, properties: CustomerSchema.writable_attributes, required: CustomerSchema.required_attributes } response "200", "Customer updated" do let(:id) { customer1.id } let(:customer) do { email: "test@example.com", enterprise_id: enterprise1.id.to_s } end schema '$ref': "#/components/schemas/customer" run_test! do # Tags should not be overridden when the param is missing: expect(json_response[:data][:attributes]).to include( tags: ["long-term"], ) end end describe "address" do it "matches by country and state code" do put "/api/v1/customers/#{customer1.id}", params: { customer: { shipping_address: CustomerSchema.address_example.merge( region: { code: "Vic" }, country: { code: "AU" }, ), } } expect(response).to have_http_status :ok expect(json_response.dig(:data, :attributes, :shipping_address)).to include( region: { code: "Vic", name: "Victoria" }, country: { code: "AU", name: "Australia" }, ) end it "matches by country and state name" do put "/api/v1/customers/#{customer1.id}", params: { customer: { shipping_address: CustomerSchema.address_example.merge( region: { name: "Victoria" }, country: { name: "Australia" }, ), } } expect(response).to have_http_status :ok expect(json_response.dig(:data, :attributes, :shipping_address)).to include( region: { code: "Vic", name: "Victoria" }, country: { code: "AU", name: "Australia" }, ) end it "matches country and state case-insensitive" do put "/api/v1/customers/#{customer1.id}", params: { customer: { shipping_address: CustomerSchema.address_example.merge( region: { code: "VIC" }, country: { name: "AUSTRALIA" }, ), } } expect(response).to have_http_status :ok expect(json_response.dig(:data, :attributes, :shipping_address)).to include( region: { code: "Vic", name: "Victoria" }, country: { code: "AU", name: "Australia" }, ) end end response "422", "Unprocessable entity" do let(:id) { customer1.id } let(:customer) { {} } schema '$ref': "#/components/schemas/error_response" run_test! end end delete "Delete customer" do tags "Customers" parameter name: :id, in: :path, type: :string produces "application/json" response "200", "Customer deleted" do let(:id) { customer1.id } schema '$ref': "#/components/schemas/customer" run_test! end end end path "/api/v1/enterprises/{enterprise_id}/customers" do get "List customers of an enterprise" do tags "Customers", "Enterprises" parameter name: :enterprise_id, in: :path, type: :string, required: true produces "application/json" response "200", "Customers list" do let(:enterprise_id) { enterprise1.id } schema '$ref': "#/components/schemas/customers_collection" run_test! end end end end