diff --git a/.github/workflows/mapi.yml b/.github/workflows/mapi.yml new file mode 100644 index 0000000000..d59dcbd089 --- /dev/null +++ b/.github/workflows/mapi.yml @@ -0,0 +1,45 @@ +name: 'Mayhem for API' +on: [push, pull_request] +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: true + steps: + - uses: actions/checkout@v2 + - run: docker/build + - run: docker-compose up --detach + - run: until curl -f -s http://localhost:3000; do echo "waiting for api server"; sleep 1; done + - run: docker-compose exec -T db psql postgresql://ofn:f00d@localhost:5432/open_food_network_dev --command="update spree_users set spree_api_key='testing' where login='ofn@example.com'" + # equivalent to Flipper.enable(:api_v1) + - run: docker-compose exec -T db psql postgresql://ofn:f00d@localhost:5432/open_food_network_dev --command="insert into flipper_features (key, created_at, updated_at) values ('api_v1', localtimestamp, localtimestamp)" + - run: docker-compose exec -T db psql postgresql://ofn:f00d@localhost:5432/open_food_network_dev --command="insert into flipper_gates (feature_key, key, value, created_at, updated_at) values ('api_v1', 'boolean', 'true', localtimestamp, localtimestamp)" + + # Run Mayhem for API + - name: Run Mayhem for API + uses: ForAllSecure/mapi-action@v1 + continue-on-error: true + with: + mapi-token: ${{ secrets.MAPI_TOKEN }} + api-url: http://localhost:3000 + api-spec: swagger/v1/swagger.yaml + target: mayhemheroes/openfoodnetwork + duration: 1min + sarif-report: mapi.sarif + html-report: mapi.html + run-args: | + --header-auth + X-Api-Token: testing + + # Archive HTML report + - name: Archive Mayhem for API report + uses: actions/upload-artifact@v2 + with: + name: mapi-report + path: mapi.html + + # Upload SARIF file (only available on public repos or github enterprise) + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@v1 + with: + sarif_file: mapi.sarif diff --git a/app/json_schemas/customer_schema.rb b/app/json_schemas/customer_schema.rb index 45529f19e3..553f4cd522 100644 --- a/app/json_schemas/customer_schema.rb +++ b/app/json_schemas/customer_schema.rb @@ -14,7 +14,7 @@ class CustomerSchema < JsonApiSchema code: { type: :string, nullable: true, example: "BUYER1" }, email: { type: :string, example: "alice@example.com" }, allow_charges: { type: :boolean, example: false }, - tags: { type: :array, example: ["staff", "discount"] }, + tags: { type: :array, items: { type: :string }, example: ["staff", "discount"] }, terms_and_conditions_accepted_at: { type: :string, format: "date-time", nullable: true, example: "2022-03-12T15:55:00.000+11:00", diff --git a/spec/requests/api/v1/customers_spec.rb b/spec/requests/api/v1/customers_spec.rb index d3a07b001e..58ae41945d 100644 --- a/spec/requests/api/v1/customers_spec.rb +++ b/spec/requests/api/v1/customers_spec.rb @@ -30,7 +30,7 @@ describe "Customers", type: :request do response "200", "Customers list" do param(:enterprise_id) { enterprise1.id } - schema "$ref": "#/components/schemas/resources/customers_collection" + schema "$ref": "#/components/schemas/customers_collection" run_test! end @@ -123,7 +123,7 @@ describe "Customers", type: :request do enterprise_id: enterprise1.id.to_s } end - schema "$ref": "#/components/schemas/resources/customer" + schema "$ref": "#/components/schemas/customer" run_test! do expect(json_response[:data][:attributes]).to include( @@ -141,7 +141,7 @@ describe "Customers", type: :request do enterprise_id: enterprise1.id, ) end - schema "$ref": "#/components/schemas/resources/customer" + schema "$ref": "#/components/schemas/customer" run_test! do expect(json_response[:data][:attributes]).to include( @@ -191,7 +191,7 @@ describe "Customers", type: :request do response "200", "Customer" do param(:id) { customer1.id } - schema "$ref": "#/components/schemas/resources/customer" + schema "$ref": "#/components/schemas/customer" run_test! do date_time_string = @@ -292,7 +292,7 @@ describe "Customers", type: :request do enterprise_id: enterprise1.id.to_s } end - schema "$ref": "#/components/schemas/resources/customer" + schema "$ref": "#/components/schemas/customer" run_test! do # Tags should not be overridden when the param is missing: @@ -318,7 +318,7 @@ describe "Customers", type: :request do response "200", "Customer deleted" do param(:id) { customer1.id } - schema "$ref": "#/components/schemas/resources/customer" + schema "$ref": "#/components/schemas/customer" run_test! end @@ -333,7 +333,7 @@ describe "Customers", type: :request do response "200", "Customers list" do param(:enterprise_id) { enterprise1.id } - schema "$ref": "#/components/schemas/resources/customers_collection" + schema "$ref": "#/components/schemas/customers_collection" run_test! end diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb index 46e7a7760d..ad4cd3c34e 100644 --- a/spec/swagger_helper.rb +++ b/spec/swagger_helper.rb @@ -27,12 +27,10 @@ RSpec.configure do |config| components: { schemas: { error_response: ErrorsSchema.schema, - resources: { - customer: CustomerSchema.schema(require_all: true), - customers_collection: CustomerSchema.collection(require_all: true) - } + customer: CustomerSchema.schema(require_all: true), + customers_collection: CustomerSchema.collection(require_all: true) }, - securitySchemas: { + securitySchemes: { api_key_header: { type: :apiKey, name: 'X-Api-Token', @@ -46,7 +44,7 @@ RSpec.configure do |config| description: "Authenticates via API key passed in specified query param" }, session: { - type: :http, + type: :apiKey, name: '_ofn_session', in: :cookie, description: "Authenticates using the current user's session if logged in" diff --git a/swagger/v1/swagger.yaml b/swagger/v1/swagger.yaml index c67e71b8a9..911fa13e74 100644 --- a/swagger/v1/swagger.yaml +++ b/swagger/v1/swagger.yaml @@ -23,11 +23,119 @@ components: - detail required: - errors - resources: - customer: - type: object - properties: - data: + customer: + type: object + properties: + data: + type: object + properties: + id: + type: string + example: '1' + type: + type: string + example: customer + attributes: + type: object + properties: + id: + type: integer + example: 1 + enterprise_id: + type: integer + example: 2 + first_name: + type: string + nullable: true + example: Alice + last_name: + type: string + nullable: true + example: Springs + code: + type: string + nullable: true + example: BUYER1 + email: + type: string + example: alice@example.com + allow_charges: + type: boolean + example: false + tags: + type: array + items: + type: string + example: + - staff + - discount + terms_and_conditions_accepted_at: + type: string + format: date-time + nullable: true + example: '2022-03-12T15:55:00.000+11:00' + billing_address: + type: object + nullable: true + example: + shipping_address: + type: object + nullable: true + example: + phone: 0404 333 222 111 + latitude: -37.8173751 + longitude: 144.964803195704 + first_name: Alice + last_name: Springs + street_address_1: 1 Flinders Street + street_address_2: '' + postal_code: '1234' + locality: Melbourne + region: Victoria + country: Australia + required: + - id + - enterprise_id + - first_name + - last_name + - code + - email + - allow_charges + - tags + - terms_and_conditions_accepted_at + - billing_address + - shipping_address + relationships: + type: object + properties: + enterprise: + type: object + properties: + data: + type: object + properties: + id: + type: string + type: + type: string + example: enterprise + links: + type: object + properties: + related: + type: string + meta: + type: object + links: + type: object + required: + - data + customers_collection: + type: object + properties: + data: + type: array + items: type: object properties: id: @@ -65,6 +173,8 @@ components: example: false tags: type: array + items: + type: string example: - staff - discount @@ -123,153 +233,46 @@ components: properties: related: type: string - meta: - type: object - links: - type: object - required: - - data - customers_collection: - type: object - properties: - data: - type: array - items: + meta: + type: object + properties: + pagination: type: object properties: - id: - type: string - example: '1' - type: - type: string - example: customer - attributes: - type: object - properties: - id: - type: integer - example: 1 - enterprise_id: - type: integer - example: 2 - first_name: - type: string - nullable: true - example: Alice - last_name: - type: string - nullable: true - example: Springs - code: - type: string - nullable: true - example: BUYER1 - email: - type: string - example: alice@example.com - allow_charges: - type: boolean - example: false - tags: - type: array - example: - - staff - - discount - terms_and_conditions_accepted_at: - type: string - format: date-time - nullable: true - example: '2022-03-12T15:55:00.000+11:00' - billing_address: - type: object - nullable: true - example: - shipping_address: - type: object - nullable: true - example: - phone: 0404 333 222 111 - latitude: -37.8173751 - longitude: 144.964803195704 - first_name: Alice - last_name: Springs - street_address_1: 1 Flinders Street - street_address_2: '' - postal_code: '1234' - locality: Melbourne - region: Victoria - country: Australia - required: - - id - - enterprise_id - - first_name - - last_name - - code - - email - - allow_charges - - tags - - terms_and_conditions_accepted_at - - billing_address - - shipping_address - relationships: - type: object - properties: - enterprise: - type: object - properties: - data: - type: object - properties: - id: - type: string - type: - type: string - example: enterprise - links: - type: object - properties: - related: - type: string - meta: - type: object - properties: - pagination: - type: object - properties: - results: - type: integer - example: 250 - pages: - type: integer - example: 5 - page: - type: integer - example: 2 - per_page: - type: integer - example: 50 - required: - - pagination - links: - type: object - properties: - self: - type: string - first: - type: string - prev: - type: string - nullable: true - next: - type: string - nullable: true - last: - type: string - required: - - data - - meta - - links - securitySchemas: + results: + type: integer + example: 250 + pages: + type: integer + example: 5 + page: + type: integer + example: 2 + per_page: + type: integer + example: 50 + required: + - pagination + links: + type: object + properties: + self: + type: string + first: + type: string + prev: + type: string + nullable: true + next: + type: string + nullable: true + last: + type: string + required: + - data + - meta + - links + securitySchemes: api_key_header: type: apiKey name: X-Api-Token @@ -281,7 +284,7 @@ components: in: query description: Authenticates via API key passed in specified query param session: - type: http + type: apiKey name: _ofn_session in: cookie description: Authenticates using the current user's session if logged in @@ -302,7 +305,7 @@ paths: content: application/json: schema: - "$ref": "#/components/schemas/resources/customers_collection" + "$ref": "#/components/schemas/customers_collection" post: summary: Create customer tags: @@ -314,7 +317,7 @@ paths: content: application/json: schema: - "$ref": "#/components/schemas/resources/customer" + "$ref": "#/components/schemas/customer" '422': description: Unprocessable entity content: @@ -347,6 +350,8 @@ paths: example: alice@example.com tags: type: array + items: + type: string example: - staff - discount @@ -389,7 +394,7 @@ paths: content: application/json: schema: - "$ref": "#/components/schemas/resources/customer" + "$ref": "#/components/schemas/customer" '404': description: Not found content: @@ -418,7 +423,7 @@ paths: content: application/json: schema: - "$ref": "#/components/schemas/resources/customer" + "$ref": "#/components/schemas/customer" '422': description: Unprocessable entity content: @@ -451,6 +456,8 @@ paths: example: alice@example.com tags: type: array + items: + type: string example: - staff - discount @@ -492,7 +499,7 @@ paths: content: application/json: schema: - "$ref": "#/components/schemas/resources/customer" + "$ref": "#/components/schemas/customer" "/api/v1/enterprises/{enterprise_id}/customers": get: summary: List customers of an enterprise @@ -511,6 +518,6 @@ paths: content: application/json: schema: - "$ref": "#/components/schemas/resources/customers_collection" + "$ref": "#/components/schemas/customers_collection" servers: - url: "/"