diff --git a/Gemfile b/Gemfile index a389ced8fb..dc4c41c531 100644 --- a/Gemfile +++ b/Gemfile @@ -144,6 +144,7 @@ group :test, :development do gem 'letter_opener', '>= 1.4.1' gem 'rspec-rails', ">= 3.5.2" gem 'rspec-retry' + gem 'rswag' gem 'selenium-webdriver' gem 'shoulda-matchers' gem 'timecop' diff --git a/Gemfile.lock b/Gemfile.lock index 550d32ab24..a62f6d8384 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -433,6 +433,8 @@ GEM jquery-ui-rails (4.2.1) railties (>= 3.2.16) json (1.8.6) + json-schema (2.8.1) + addressable (>= 2.4) json_spec (1.1.5) multi_json (~> 1.0) rspec (>= 2.0, < 4.0) @@ -586,6 +588,19 @@ GEM rspec-retry (0.6.2) rspec-core (> 3.3) rspec-support (3.9.2) + rswag (2.2.0) + rswag-api (= 2.2.0) + rswag-specs (= 2.2.0) + rswag-ui (= 2.2.0) + rswag-api (2.2.0) + railties (>= 3.1, < 6.1) + rswag-specs (2.2.0) + activesupport (>= 3.1, < 6.1) + json-schema (~> 2.2) + railties (>= 3.1, < 6.1) + rswag-ui (2.2.0) + actionpack (>= 3.1, < 6.1) + railties (>= 3.1, < 6.1) rubocop (0.81.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) @@ -775,6 +790,7 @@ DEPENDENCIES roo (~> 2.8.3) rspec-rails (>= 3.5.2) rspec-retry + rswag rubocop rubocop-rails sass diff --git a/app/controllers/api/orders_controller.rb b/app/controllers/api/orders_controller.rb index c011db44ec..7339af3c61 100644 --- a/app/controllers/api/orders_controller.rb +++ b/app/controllers/api/orders_controller.rb @@ -8,7 +8,7 @@ module Api def index authorize! :admin, Spree::Order - search_results = SearchOrders.new(params, spree_current_user) + search_results = SearchOrders.new(params, current_api_user) render json: { orders: serialized_orders(search_results.orders), diff --git a/config/routes.rb b/config/routes.rb index 877ca407d3..ef3380ea79 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Openfoodnetwork::Application.routes.draw do + root :to => 'home#index' # Redirects from old URLs avoid server errors and helps search engines diff --git a/spec/requests/api/orders_spec.rb b/spec/requests/api/orders_spec.rb new file mode 100644 index 0000000000..b282b4045a --- /dev/null +++ b/spec/requests/api/orders_spec.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +require 'swagger_helper' + +describe 'api/orders', type: :request do + path '/api/orders' do + get('list orders') do + tags 'Orders' + # type should be replaced with swagger 3.01 valid schema: {type: string} when rswag #317 is resolved: + # https://github.com/rswag/rswag/pull/319 + parameter name: 'X-Spree-Token', in: :header, type: :string + parameter name: 'q[distributor_id_eq]', in: :query, type: :string, required: false, description: "Query orders for a specific distributor id." + parameter name: 'q[completed_at_gt]', in: :query, type: :string, required: false, description: "Query orders completed after a date." + parameter name: 'q[completed_at_lt]', in: :query, type: :string, required: false, description: "Query orders completed before a date." + parameter name: 'q[state_eq]', in: :query, type: :string, required: false, description: "Query orders by order state, eg 'cart', 'complete'." + parameter name: 'q[payment_state_eq]', in: :query, type: :string, required: false, description: "Query orders by order payment_state, eg 'balance_due', 'paid', 'failed'." + parameter name: 'q[email_cont]', in: :query, type: :string, required: false, description: "Query orders where the order email contains a string." + parameter name: 'q[order_cycle_id_eq]', in: :query, type: :string, required: false, description: "Query orders for a specific order_cycle id." + + response(200, 'get orders') do + # Adds model metadata for Swagger UI. Ideally we'd be able to just add: + # schema '$ref' => '#/components/schemas/Order_Concise' + # Which would also validate the response in the test, this is an open + # issue with rswag: https://github.com/rswag/rswag/issues/268 + metadata[:response][:content] = { + "application/json": { + schema: {'$ref' => '#/components/schemas/Order_Concise'} + } + } + context "when there are four orders with different properties set" do + let!(:order_dist_1) { create(:order_with_distributor, email: "specific_name@example.com") } + let!(:order_dist_2) { create(:order_with_totals_and_distribution) } + let!(:order_dist_1_complete) { create(:order, distributor: order_dist_1.distributor, state: 'complete', completed_at: Time.zone.today - 7.days) } + let!(:order_dist_1_credit_owed) { create(:order, distributor: order_dist_1.distributor, payment_state: 'credit_owed', completed_at: Time.zone.today) } + + let(:user) { order_dist_1.distributor.owner } + let(:'X-Spree-Token') do + user.generate_spree_api_key! + user.spree_api_key + end + + context "and there are no query parameters" do + run_test! do |response| + expect(response).to have_http_status(200) + + data = JSON.parse(response.body) + orders = data["orders"] + expect(orders.size).to eq 4 + end + end + + context "and queried by distributor id" do + let(:'q[distributor_id_eq]') { order_dist_2.distributor.id } + + before { order_dist_2.distributor.update_attributes owner: user } + + run_test! do |response| + expect(response).to have_http_status(200) + + data = JSON.parse(response.body) + orders = data["orders"] + expect(orders.size).to eq 1 + expect(orders.first["id"]).to eq order_dist_2.id + end + end + + context "and queried within a date range" do + let(:'q[completed_at_gt]') { Time.zone.today - 7.days - 1.second } + let(:'q[completed_at_lt]') { Time.zone.today - 6.days } + + run_test! do |response| + expect(response).to have_http_status(200) + + data = JSON.parse(response.body) + orders = data["orders"] + expect(orders.size).to eq 1 + expect(orders.first["id"]).to eq order_dist_1_complete.id + end + end + + context "and queried by complete state" do + let(:'q[state_eq]') { "complete" } + run_test! do |response| + expect(response).to have_http_status(200) + + data = JSON.parse(response.body) + orders = data["orders"] + expect(orders.size).to eq 1 + expect(orders.first["id"]).to eq order_dist_1_complete.id + end + end + + context "and queried by credit_owed payment_state" do + let(:'q[payment_state_eq]') { "credit_owed" } + run_test! do |response| + expect(response).to have_http_status(200) + + data = JSON.parse(response.body) + orders = data["orders"] + expect(orders.size).to eq 1 + expect(orders.first["id"]).to eq order_dist_1_credit_owed.id + end + end + + context "and queried by buyer email contains a specific string" do + let(:'q[email_cont]') { order_dist_1.email.split("@").first } + run_test! do |response| + expect(response).to have_http_status(200) + + data = JSON.parse(response.body) + orders = data["orders"] + expect(orders.size).to eq 1 + expect(orders.first["id"]).to eq order_dist_1.id + end + end + + context "and queried by a specific order_cycle" do + let(:'q[order_cycle_id_eq]') { + order_dist_2.order_cycle.id + } + + before { order_dist_2.distributor.update_attributes owner: user } + + run_test! do |response| + expect(response).to have_http_status(200) + + data = JSON.parse(response.body) + orders = data["orders"] + expect(orders.size).to eq 1 + expect(orders.first["id"]).to eq order_dist_2.id + end + end + end + end + end + end +end diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb new file mode 100644 index 0000000000..0bf64f9ced --- /dev/null +++ b/spec/swagger_helper.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.configure do |config| + config.swagger_root = Rails.root.join('swagger').to_s + config.swagger_docs = { + 'v1/swagger.yaml' => { + openapi: '3.0.1', + info: { + title: 'The Open Food Network', + description: 'This spec is auto generated using the rswag gem. It is incomplete and not yet valid for openapi 3.0.1. Do not publish this. \ +Some endpoints are public and require no authorization; others require authorization. Talk to us to get your credentials set up. \ +Check out our repo! https://github.com/openfoodfoundation/openfoodnetwork', + version: '0.1', + }, + components: { + securitySchemes: { + api_key: { + type: :apiKey, + name: 'X-Spree-Token', + in: :header + } + }, + schemas: { + Order_Concise: { + type: 'object', + properties: { + id: { type: 'integer' }, + number: { type: 'string' }, + full_name: { type: 'string' }, + email: { type: 'string' }, + phone: { type: 'string' }, + completed_at: { type: 'string' }, + display_total: { type: 'string' }, + show_path: { type: 'string' }, + edit_path: { type: 'string' }, + state: { type: 'string' }, + payment_state: { type: 'string' }, + shipment_state: { type: 'string' }, + payments_path: { type: 'string' }, + shipments_path: { type: 'string' }, + ship_path: { type: 'string' }, + ready_to_ship: { type: 'string' }, + created_at: { type: 'string' }, + distributor_name: { type: 'string' }, + special_instructions: { type: 'string' }, + payment_capture_path: { type: 'string' }, + distributor: { + type: 'object', + properties: { + id: { type: 'integer' } + } + }, + order_cycle: { + type: 'object', + properties: { + id: { type: 'integer' } + } + } + } + } + } + }, + paths: {}, + servers: [ + { + url: 'https://staging.katuma.org/api' + } + ] + } + } + config.swagger_format = :yaml +end diff --git a/swagger.yaml b/swagger.yaml index 838f09f2d4..9f4accc980 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -342,9 +342,63 @@ paths: /orders: get: - description: Gets all Orders. + description: > + Gets all Orders. Use combinations of parameters to filter your query. + For example /api/orders?q[completed_at_gt]=2020_02_02&q[completed_at_lt]=2020_02_10 returns orders between 2nd and 10th February 2020. + Query parameters are generated for the '#/components/schemas/Order_Concise' model with [Ransack](https://github.com/activerecord-hackery/ransack#search-matchers) search matchers tags: - orders + parameters: + - in: query + name: q[distributor_id_eq] + schema: + type: string + style: deepObject + description: Query orders for a specific distributor id. + required: false + - in: query + name: q[completed_at_gt] + schema: + type: string + style: deepObject + description: Query orders completed after a date. + required: false + - in: query + name: q[completed_at_lt] + schema: + type: string + style: deepObject + description: Query orders completed before a date. + required: false + - in: query + name: q[state_eq] + schema: + type: string + style: deepObject + description: Query orders by order state, eg 'cart', 'complete'. + required: false + - in: query + name: q[payment_state_eq] + schema: + type: string + style: deepObject + description: Query orders by order payment_state, eg 'balance_due', 'paid', 'failed'. + required: false + - in: query + name: q[email_cont] + schema: + type: string + style: deepObject + description: Query orders where the order email contains a string. + required: false + - in: query + name: q[order_cycle_id_eq] + schema: + type: string + style: deepObject + description: Query orders for a specific order_cycle id. + required: false + responses: '200': description: successful operation diff --git a/swagger/v1/swagger.yaml b/swagger/v1/swagger.yaml new file mode 100644 index 0000000000..88d54ed960 --- /dev/null +++ b/swagger/v1/swagger.yaml @@ -0,0 +1,124 @@ +--- +openapi: 3.0.1 +info: + title: The Open Food Network + description: |- + This spec is auto generated using the rswag gem. It is incomplete and not yet valid for openapi 3.0.1. Do not publish this. \ + Some endpoints are public and require no authorization; others require authorization. Talk to us to get your credentials set up. \ + Check out our repo! https://github.com/openfoodfoundation/openfoodnetwork + version: '0.1' +components: + securitySchemes: + api_key: + type: apiKey + name: X-Spree-Token + in: header + schemas: + Order_Concise: + type: object + properties: + id: + type: integer + number: + type: string + full_name: + type: string + email: + type: string + phone: + type: string + completed_at: + type: string + display_total: + type: string + show_path: + type: string + edit_path: + type: string + state: + type: string + payment_state: + type: string + shipment_state: + type: string + payments_path: + type: string + shipments_path: + type: string + ship_path: + type: string + ready_to_ship: + type: string + created_at: + type: string + distributor_name: + type: string + special_instructions: + type: string + payment_capture_path: + type: string + distributor: + type: object + properties: + id: + type: integer + order_cycle: + type: object + properties: + id: + type: integer +paths: + "/api/orders": + get: + summary: list orders + tags: + - Orders + parameters: + - name: X-Spree-Token + in: header + type: string + - name: q[distributor_id_eq] + in: query + type: string + required: false + description: Query orders for a specific distributor id. + - name: q[completed_at_gt] + in: query + type: string + required: false + description: Query orders completed after a date. + - name: q[completed_at_lt] + in: query + type: string + required: false + description: Query orders completed before a date. + - name: q[state_eq] + in: query + type: string + required: false + description: Query orders by order state, eg 'cart', 'complete'. + - name: q[payment_state_eq] + in: query + type: string + required: false + description: Query orders by order payment_state, eg 'balance_due', 'paid', + 'failed'. + - name: q[email_cont] + in: query + type: string + required: false + description: Query orders where the order email contains a string. + - name: q[order_cycle_id_eq] + in: query + type: string + required: false + description: Query orders for a specific order_cycle id. + responses: + '200': + description: get orders + content: + application/json: + schema: + "$ref": "#/components/schemas/Order_Concise" +servers: +- url: https://staging.katuma.org/api