├── .circleci └── config.yml ├── .gem_release.yml ├── .github ├── dependabot.yml └── stale.yml ├── .github_changelog_generator ├── .gitignore ├── .rspec ├── .rubocop.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile-local ├── LICENSE ├── LICENSE.md ├── OLD_CHANGELOG.md ├── README.md ├── Rakefile ├── app ├── assets │ ├── javascripts │ │ └── spree │ │ │ ├── backend │ │ │ └── solidus_graphql_api.js │ │ │ └── frontend │ │ │ └── solidus_graphql_api.js │ └── stylesheets │ │ └── spree │ │ ├── backend │ │ └── solidus_graphql_api.css │ │ └── frontend │ │ └── solidus_graphql_api.css └── controllers │ └── spree │ └── graphql_controller.rb ├── bin ├── console ├── rails ├── rails-engine ├── rails-sandbox ├── rake ├── sandbox └── setup ├── config ├── locales │ └── en.yml └── routes.rb ├── docs ├── assets │ ├── images │ │ ├── graphiql-headers.png │ │ ├── graphiql-variables.png │ │ ├── graphiql.png │ │ ├── menu.png │ │ └── navbar.png │ ├── style.css │ └── webfonts │ │ ├── 2C4B9D_B_0.eot │ │ ├── 2C4B9D_B_0.ttf │ │ ├── 2C4B9D_B_0.woff │ │ ├── 2C4B9D_B_0.woff2 │ │ ├── 2C4B9D_C_0.eot │ │ ├── 2C4B9D_C_0.ttf │ │ ├── 2C4B9D_C_0.woff │ │ ├── 2C4B9D_C_0.woff2 │ │ ├── 2C4B9D_D_0.eot │ │ ├── 2C4B9D_D_0.ttf │ │ ├── 2C4B9D_D_0.woff │ │ ├── 2C4B9D_D_0.woff2 │ │ ├── 2C4B9D_E_0.eot │ │ ├── 2C4B9D_E_0.ttf │ │ ├── 2C4B9D_E_0.woff │ │ └── 2C4B9D_E_0.woff2 ├── directive │ ├── deprecated │ │ └── index.html │ ├── include │ │ └── index.html │ ├── index.html │ └── skip │ │ └── index.html ├── enum │ ├── __directivelocation │ │ └── index.html │ ├── __typekind │ │ └── index.html │ ├── addresstypeinput │ │ └── index.html │ └── index.html ├── index.html ├── input_object │ ├── addaddressestocheckoutinput │ │ └── index.html │ ├── addpaymenttocheckoutinput │ │ └── index.html │ ├── addressinput │ │ └── index.html │ ├── addtocartinput │ │ └── index.html │ ├── advancecheckoutinput │ │ └── index.html │ ├── applycouponcodeinput │ │ └── index.html │ ├── completecheckoutinput │ │ └── index.html │ ├── createorderinput │ │ └── index.html │ ├── emptycartinput │ │ └── index.html │ ├── index.html │ ├── markdefaultshipaddressinput │ │ └── index.html │ ├── nextcheckoutstateinput │ │ └── index.html │ ├── productsqueryinput │ │ └── index.html │ ├── removefromaddressbookinput │ │ └── index.html │ ├── removefromcartinput │ │ └── index.html │ ├── saveinaddressbookinput │ │ └── index.html │ ├── selectshippingrateinput │ │ └── index.html │ ├── setorderemailinput │ │ └── index.html │ └── updatecartquantityinput │ │ └── index.html ├── interface │ ├── adjustment │ │ └── index.html │ ├── index.html │ ├── node │ │ └── index.html │ └── paymentsource │ │ └── index.html ├── mutation │ ├── addaddressestocheckout │ │ └── index.html │ ├── addpaymenttocheckout │ │ └── index.html │ ├── addtocart │ │ └── index.html │ ├── advancecheckout │ │ └── index.html │ ├── applycouponcode │ │ └── index.html │ ├── completecheckout │ │ └── index.html │ ├── createorder │ │ └── index.html │ ├── emptycart │ │ └── index.html │ ├── markdefaultshipaddress │ │ └── index.html │ ├── nextcheckoutstate │ │ └── index.html │ ├── removefromaddressbook │ │ └── index.html │ ├── removefromcart │ │ └── index.html │ ├── saveinaddressbook │ │ └── index.html │ ├── selectshippingrate │ │ └── index.html │ ├── setorderemail │ │ └── index.html │ └── updatecartquantity │ │ └── index.html ├── object │ ├── __directive │ │ └── index.html │ ├── __enumvalue │ │ └── index.html │ ├── __field │ │ └── index.html │ ├── __inputvalue │ │ └── index.html │ ├── __schema │ │ └── index.html │ ├── __type │ │ └── index.html │ ├── addaddressestocheckoutpayload │ │ └── index.html │ ├── addpaymenttocheckoutpayload │ │ └── index.html │ ├── address │ │ └── index.html │ ├── addressconnection │ │ └── index.html │ ├── addressedge │ │ └── index.html │ ├── addtocartpayload │ │ └── index.html │ ├── adjustmentconnection │ │ └── index.html │ ├── adjustmentedge │ │ └── index.html │ ├── advancecheckoutpayload │ │ └── index.html │ ├── applycouponcodepayload │ │ └── index.html │ ├── completecheckoutpayload │ │ └── index.html │ ├── country │ │ └── index.html │ ├── countryconnection │ │ └── index.html │ ├── countryedge │ │ └── index.html │ ├── createorderpayload │ │ └── index.html │ ├── creditcard │ │ └── index.html │ ├── currency │ │ └── index.html │ ├── currencyconnection │ │ └── index.html │ ├── currencyedge │ │ └── index.html │ ├── emptycartpayload │ │ └── index.html │ ├── image │ │ └── index.html │ ├── imageconnection │ │ └── index.html │ ├── imageedge │ │ └── index.html │ ├── index.html │ ├── lineitem │ │ └── index.html │ ├── lineitemconnection │ │ └── index.html │ ├── lineitemedge │ │ └── index.html │ ├── manifestitem │ │ └── index.html │ ├── markdefaultshipaddresspayload │ │ └── index.html │ ├── nextcheckoutstatepayload │ │ └── index.html │ ├── optiontype │ │ └── index.html │ ├── optiontypeconnection │ │ └── index.html │ ├── optiontypeedge │ │ └── index.html │ ├── optionvalue │ │ └── index.html │ ├── optionvalueconnection │ │ └── index.html │ ├── optionvalueedge │ │ └── index.html │ ├── order │ │ └── index.html │ ├── orderconnection │ │ └── index.html │ ├── orderedge │ │ └── index.html │ ├── pageinfo │ │ └── index.html │ ├── payment │ │ └── index.html │ ├── paymentmethod │ │ └── index.html │ ├── price │ │ └── index.html │ ├── priceconnection │ │ └── index.html │ ├── priceedge │ │ └── index.html │ ├── product │ │ └── index.html │ ├── productconnection │ │ └── index.html │ ├── productedge │ │ └── index.html │ ├── productproperty │ │ └── index.html │ ├── productpropertyconnection │ │ └── index.html │ ├── productpropertyedge │ │ └── index.html │ ├── promotionadjustment │ │ └── index.html │ ├── property │ │ └── index.html │ ├── removefromaddressbookpayload │ │ └── index.html │ ├── removefromcartpayload │ │ └── index.html │ ├── saveinaddressbookpayload │ │ └── index.html │ ├── selectshippingratepayload │ │ └── index.html │ ├── setorderemailpayload │ │ └── index.html │ ├── shipment │ │ └── index.html │ ├── shipmentconnection │ │ └── index.html │ ├── shipmentedge │ │ └── index.html │ ├── shippingmethod │ │ └── index.html │ ├── shippingrate │ │ └── index.html │ ├── shippingrateconnection │ │ └── index.html │ ├── shippingrateedge │ │ └── index.html │ ├── state │ │ └── index.html │ ├── stateconnection │ │ └── index.html │ ├── stateedge │ │ └── index.html │ ├── store │ │ └── index.html │ ├── taxadjustment │ │ └── index.html │ ├── taxon │ │ └── index.html │ ├── taxonconnection │ │ └── index.html │ ├── taxonedge │ │ └── index.html │ ├── taxonomy │ │ └── index.html │ ├── taxonomyconnection │ │ └── index.html │ ├── taxonomyedge │ │ └── index.html │ ├── updatecartquantitypayload │ │ └── index.html │ ├── user │ │ └── index.html │ ├── usererror │ │ └── index.html │ ├── variant │ │ └── index.html │ ├── variantconnection │ │ └── index.html │ ├── variantedge │ │ └── index.html │ ├── walletpaymentsource │ │ └── index.html │ ├── walletpaymentsourceconnection │ │ └── index.html │ └── walletpaymentsourceedge │ │ └── index.html ├── operation │ ├── mutation │ │ └── index.html │ └── query │ │ └── index.html ├── scalar │ ├── boolean │ │ └── index.html │ ├── float │ │ └── index.html │ ├── id │ │ └── index.html │ ├── index.html │ ├── int │ │ └── index.html │ ├── iso8601datetime │ │ └── index.html │ ├── json │ │ └── index.html │ └── string │ │ └── index.html └── union │ └── index.html ├── lib ├── generators │ └── solidus_graphql_api │ │ └── install │ │ ├── install_generator.rb │ │ └── templates │ │ └── initializer.rb ├── graphql_docs │ └── landing_pages │ │ └── index.md ├── solidus_graphql_api.rb └── solidus_graphql_api │ ├── batch_loader.rb │ ├── batch_loader │ ├── belongs_to.rb │ ├── has_many.rb │ ├── has_many_through.rb │ └── has_one.rb │ ├── configuration.rb │ ├── context.rb │ ├── engine.rb │ ├── mutations │ ├── .keep │ ├── base_mutation.rb │ ├── checkout │ │ ├── add_addresses_to_checkout.rb │ │ ├── add_payment_to_checkout.rb │ │ ├── add_to_cart.rb │ │ ├── advance_checkout.rb │ │ ├── apply_coupon_code.rb │ │ ├── complete_checkout.rb │ │ ├── create_order.rb │ │ ├── empty_cart.rb │ │ ├── next_checkout_state.rb │ │ ├── remove_from_cart.rb │ │ ├── select_shipping_rate.rb │ │ ├── set_order_email.rb │ │ └── update_cart_quantity.rb │ └── user │ │ ├── mark_default_ship_address.rb │ │ ├── remove_from_address_book.rb │ │ └── save_in_address_book.rb │ ├── queries │ ├── address │ │ ├── country_query.rb │ │ └── state_query.rb │ ├── completed_orders_query.rb │ ├── countries_query.rb │ ├── country │ │ └── states_query.rb │ ├── line_item │ │ └── variant_query.rb │ ├── option_type │ │ └── option_values_query.rb │ ├── option_value │ │ └── option_type_query.rb │ ├── order │ │ ├── adjustments_query.rb │ │ ├── billing_address_query.rb │ │ ├── line_items_query.rb │ │ ├── payments_query.rb │ │ ├── shipments_query.rb │ │ └── shipping_address_query.rb │ ├── product │ │ ├── master_variant_query.rb │ │ ├── option_types_query.rb │ │ ├── product_properties_query.rb │ │ └── variants_query.rb │ ├── product_by_slug_query.rb │ ├── product_property │ │ └── property_query.rb │ ├── products_query.rb │ ├── shipment │ │ └── shipping_rates_query.rb │ ├── shipping_rate │ │ └── shipping_method_query.rb │ ├── taxon │ │ ├── children_query.rb │ │ └── parent_taxon_query.rb │ ├── taxonomies_query.rb │ ├── taxonomy │ │ ├── root_taxon_query.rb │ │ └── taxons_query.rb │ └── variant │ │ ├── default_price_query.rb │ │ ├── images_query.rb │ │ ├── option_values_query.rb │ │ └── prices_query.rb │ ├── schema.rb │ ├── testing_support │ ├── factories.rb │ └── factories │ │ ├── address_factory.rb │ │ ├── country_factory.rb │ │ ├── store_factory.rb │ │ └── taxonomy_factory.rb │ ├── types │ ├── .keep │ ├── address.rb │ ├── base │ │ ├── argument.rb │ │ ├── enum.rb │ │ ├── field.rb │ │ ├── input_object.rb │ │ ├── interface.rb │ │ ├── object.rb │ │ ├── relay_node.rb │ │ ├── scalar.rb │ │ └── union.rb │ ├── country.rb │ ├── credit_card.rb │ ├── currency.rb │ ├── image.rb │ ├── input_objects │ │ ├── address_input.rb │ │ └── products_query_input.rb │ ├── interfaces │ │ ├── adjustment.rb │ │ └── payment_source.rb │ ├── json.rb │ ├── line_item.rb │ ├── manifest_item.rb │ ├── mutation.rb │ ├── option_type.rb │ ├── option_value.rb │ ├── order.rb │ ├── payment.rb │ ├── payment_method.rb │ ├── price.rb │ ├── product.rb │ ├── product_property.rb │ ├── promotion_adjustment.rb │ ├── property.rb │ ├── query.rb │ ├── shipment.rb │ ├── shipping_method.rb │ ├── shipping_rate.rb │ ├── state.rb │ ├── store.rb │ ├── tax_adjustment.rb │ ├── taxon.rb │ ├── taxonomy.rb │ ├── user.rb │ ├── user_error.rb │ ├── variant.rb │ └── wallet_payment_source.rb │ └── version.rb ├── schema.graphql ├── solidus_graphql_api.gemspec └── spec ├── integration ├── mutations │ ├── add_to_cart_spec.rb │ ├── checkout │ │ ├── add_addresses_to_checkout_spec.rb │ │ ├── add_payment_to_checkout_spec.rb │ │ ├── advance_checkout_spec.rb │ │ ├── apply_coupon_code_spec.rb │ │ ├── complete_checkout_spec.rb │ │ ├── next_checkout_state_spec.rb │ │ └── select_shipping_rate_spec.rb │ ├── create_order_spec.rb │ ├── empty_cart_spec.rb │ ├── mark_default_ship_address_spec.rb │ ├── remove_from_address_book_spec.rb │ ├── remove_from_cart_spec.rb │ ├── save_in_address_book_spec.rb │ ├── set_order_email_spec.rb │ └── update_cart_quantity_spec.rb ├── queries │ ├── completed_orders_spec.rb │ ├── countries_spec.rb │ ├── current_order_spec.rb │ ├── current_store_spec.rb │ ├── current_user_spec.rb │ ├── product_by_slug_spec.rb │ ├── products_spec.rb │ └── taxonomies_spec.rb └── types │ └── json_spec.rb ├── lib └── solidus_graphql_api │ ├── batch_loader │ ├── belongs_to_spec.rb │ ├── has_many_spec.rb │ ├── has_many_through_spec.rb │ └── has_one_spec.rb │ ├── context_spec.rb │ ├── queries │ ├── address │ │ ├── country_query_spec.rb │ │ └── state_query_spec.rb │ ├── completed_orders_query_spec.rb │ ├── countries_query_spec.rb │ ├── country │ │ └── states_query_spec.rb │ ├── line_item │ │ └── variant_query_spec.rb │ ├── option_type │ │ └── option_values_query_spec.rb │ ├── option_value │ │ └── option_type_query_spec.rb │ ├── order │ │ ├── adjustments_query_spec.rb │ │ ├── billing_address_query_spec.rb │ │ ├── line_items_query_spec.rb │ │ ├── payments_query_spec.rb │ │ ├── shipments_query_spec.rb │ │ └── shipping_address_query_spec.rb │ ├── product │ │ ├── master_variant_query_spec.rb │ │ ├── option_types_query_spec.rb │ │ ├── product_properties_query_spec.rb │ │ └── variants_query_spec.rb │ ├── product_by_slug_query_spec.rb │ ├── product_property │ │ └── property_query_spec.rb │ ├── products_query_spec.rb │ ├── shipment │ │ └── shipping_rates_query_spec.rb │ ├── shipping_rate │ │ └── shipping_method_query_spec.rb │ ├── taxon │ │ ├── children_query_spec.rb │ │ └── parent_taxon_query_spec.rb │ ├── taxonomies_query_spec.rb │ ├── taxonomy │ │ ├── root_taxon_query_spec.rb │ │ └── taxons_query_spec.rb │ └── variant │ │ ├── default_price_query_spec.rb │ │ ├── images_query_spec.rb │ │ ├── option_values_query_spec.rb │ │ └── prices_query_spec.rb │ ├── schema_spec.rb │ └── types │ ├── base │ └── object_spec.rb │ ├── json_spec.rb │ ├── option_type_spec.rb │ ├── option_value_spec.rb │ ├── product_property_spec.rb │ ├── product_spec.rb │ ├── query_spec.rb │ ├── shipping_rate_spec.rb │ └── variant_spec.rb ├── requests └── spree │ └── graphql_controller_spec.rb ├── spec_helper.rb └── support ├── expected_schema.graphql ├── graphql ├── mutations │ ├── add_addresses_to_checkout.gql │ ├── add_payment_to_checkout.gql │ ├── add_to_cart.gql │ ├── advance_checkout.gql │ ├── apply_coupon_code.gql │ ├── complete_checkout.gql │ ├── create_order.gql │ ├── empty_cart.gql │ ├── mark_default_ship_address.gql │ ├── next_checkout_state.gql │ ├── remove_from_address_book.gql │ ├── remove_from_cart.gql │ ├── save_in_address_book.gql │ ├── select_shipping_rate.gql │ ├── set_order_email.gql │ └── update_cart_quantity.gql ├── queries │ ├── completed_orders.gql │ ├── completed_orders │ │ ├── adjustments.gql │ │ ├── available_payment_methods.gql │ │ ├── billing_address.gql │ │ ├── line_items.gql │ │ ├── payments.gql │ │ └── shipping_address.gql │ ├── countries.gql │ ├── current_order.gql │ ├── current_store.gql │ ├── current_user.gql │ ├── product_by_slug.gql │ ├── products.gql │ └── taxonomies.gql └── responses │ ├── completed_orders.json.erb │ ├── completed_orders │ ├── adjustments.json.erb │ ├── available_payment_methods.json.erb │ ├── billing_address.json.erb │ ├── line_items.json.erb │ ├── payments.json.erb │ └── shipping_address.json.erb │ ├── countries.json.erb │ ├── current_order.json.erb │ ├── current_store.json.erb │ ├── current_user.json.erb │ ├── product_by_slug.json.erb │ └── taxonomies.json.erb ├── helpers ├── active_record.rb └── graphql.rb ├── matchers └── graphql.rb └── shared_contexts └── graphql_query_subject.rb /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | # Always take the latest version of the orb, this allows us to 5 | # run specs against Solidus supported versions only without the need 6 | # to change this configuration every time a Solidus version is released 7 | # or goes EOL. 8 | solidusio_extensions: solidusio/extensions@volatile 9 | 10 | commands: 11 | libvips: 12 | steps: 13 | - run: 14 | name: Install libvips 15 | command: "sudo apt-get update && sudo apt-get install -y libvips" 16 | 17 | jobs: 18 | run-specs: 19 | parameters: 20 | solidus: 21 | type: string 22 | default: main 23 | db: 24 | type: string 25 | default: "postgres" 26 | ruby: 27 | type: string 28 | default: "3.2" 29 | executor: 30 | name: solidusio_extensions/<< parameters.db >> 31 | ruby_version: << parameters.ruby >> 32 | steps: 33 | - checkout 34 | - libvips 35 | - solidusio_extensions/run-tests-solidus-<< parameters.solidus >> 36 | 37 | workflows: 38 | "Run specs on supported Solidus versions": 39 | jobs: 40 | - run-specs: 41 | name: &name "run-specs-solidus-<< matrix.solidus >>-ruby-<< matrix.ruby >>-db-<< matrix.db >>" 42 | matrix: 43 | parameters: { solidus: ["main"], ruby: ["3.2"], db: ["postgres"] } 44 | - run-specs: 45 | name: *name 46 | matrix: 47 | parameters: { solidus: ["current"], ruby: ["3.1"], db: ["mysql"] } 48 | - run-specs: 49 | name: *name 50 | matrix: 51 | parameters: { solidus: ["older"], ruby: ["3.0"], db: ["sqlite"] } 52 | 53 | "Weekly run specs against main": 54 | triggers: 55 | - schedule: 56 | cron: "0 0 * * 4" # every Thursday 57 | filters: 58 | branches: 59 | only: 60 | - main 61 | jobs: 62 | - run-specs: 63 | name: *name 64 | matrix: 65 | parameters: { solidus: ["main"], ruby: ["3.2"], db: ["postgres"] } 66 | - run-specs: 67 | name: *name 68 | matrix: 69 | parameters: { solidus: ["current"], ruby: ["3.1"], db: ["mysql"] } 70 | -------------------------------------------------------------------------------- /.gem_release.yml: -------------------------------------------------------------------------------- 1 | bump: 2 | recurse: false 3 | file: 'lib/solidus_graphql_api/version.rb' 4 | message: Bump SolidusGraphqlApi to %{version} 5 | tag: true 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: solidus_dev_support 10 | versions: 11 | - 2.1.0 12 | - 2.4.0 13 | - 2.4.2 14 | - 2.4.3 15 | - dependency-name: pry 16 | versions: 17 | - 0.14.0 18 | - dependency-name: graphql 19 | versions: 20 | - 1.12.0 21 | - dependency-name: simplecov 22 | versions: 23 | - 0.21.2 24 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: false 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: stale 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It might be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github_changelog_generator: -------------------------------------------------------------------------------- 1 | issues=false 2 | exclude-labels=infrastructure 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | \#* 3 | *~ 4 | .#* 5 | .DS_Store 6 | .idea 7 | .project 8 | .sass-cache 9 | coverage 10 | Gemfile.lock 11 | tmp 12 | nbproject 13 | pkg 14 | *.swp 15 | spec/dummy 16 | spec/examples.txt 17 | /sandbox 18 | .rvmrc 19 | .ruby-version 20 | .ruby-gemset 21 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - solidus_dev_support/rubocop 3 | 4 | AllCops: 5 | TargetRubyVersion: 3.0 6 | 7 | Style/ExpandPathArguments: 8 | Enabled: false 9 | 10 | Performance/RegexpMatch: 11 | Enabled: false 12 | 13 | RSpec/MessageSpies: 14 | Enabled: false 15 | 16 | RSpec/NotToNot: 17 | Enabled: false 18 | 19 | RSpec/NamedSubject: 20 | Enabled: false 21 | 22 | RSpec/VerifiedDoubles: 23 | Enabled: false 24 | 25 | RSpec/LeadingSubject: 26 | Enabled: false 27 | 28 | RSpec/ContextWording: 29 | Enabled: false 30 | 31 | RSpec/NestedGroups: 32 | Enabled: false 33 | 34 | RSpec/PredicateMatcher: 35 | Enabled: false 36 | 37 | RSpec/DescribeClass: 38 | Enabled: false 39 | 40 | RSpec/DescribedClass: 41 | Enabled: false 42 | 43 | RSpec/LeakyConstantDeclaration: 44 | Enabled: false 45 | 46 | RSpec/AnyInstance: 47 | Enabled: false 48 | 49 | RSpec/LetSetup: 50 | Enabled: false 51 | 52 | RSpec/HookArgument: 53 | Enabled: false 54 | 55 | Layout/ArgumentAlignment: 56 | Enabled: false 57 | 58 | Rails/RakeEnvironment: 59 | Enabled: false 60 | 61 | Rails/HttpStatus: 62 | Enabled: false 63 | 64 | RSpec/MultipleMemoizedHelpers: 65 | Enabled: false 66 | 67 | Lint/ConstantDefinitionInBlock: 68 | Enabled: false 69 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | See https://github.com/solidusio/solidus_graphql_api/releases or [OLD_CHANGELOG.md](OLD_CHANGELOG.md) for older versions. 4 | 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 5 | 6 | branch = ENV.fetch('SOLIDUS_BRANCH', 'main') 7 | gem 'solidus', github: 'solidusio/solidus', branch: branch 8 | 9 | # Needed to help Bundler figure out how to resolve dependencies, 10 | # otherwise it takes forever to resolve them. 11 | # See https://github.com/bundler/bundler/issues/6677 12 | gem 'rails', '>0.a' 13 | 14 | # Provides basic authentication functionality for testing parts of your engine 15 | gem 'solidus_auth_devise' 16 | 17 | case ENV['DB'] 18 | when 'mysql' 19 | gem 'mysql2' 20 | when 'postgresql' 21 | gem 'pg' 22 | else 23 | gem 'sqlite3' 24 | end 25 | 26 | gemspec 27 | 28 | # Use a local Gemfile to include development dependencies that might not be 29 | # relevant for the project or for other contributors, e.g. pry-byebug. 30 | # 31 | # We use `send` instead of calling `eval_gemfile` to work around an issue with 32 | # how Dependabot parses projects: https://github.com/dependabot/dependabot-core/issues/1658. 33 | send(:eval_gemfile, 'Gemfile-local') if File.exist? 'Gemfile-local' 34 | -------------------------------------------------------------------------------- /Gemfile-local: -------------------------------------------------------------------------------- 1 | gem "pry-byebug" 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-2021 Nebulab 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | * Neither the name Solidus nor the names of its contributors may be used to 13 | endorse or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 [name of plugin creator] 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | * Neither the name Solidus nor the names of its contributors may be used to 13 | endorse or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'solidus_dev_support/rake_tasks' 5 | require 'graphql-docs' 6 | 7 | SolidusDevSupport::RakeTasks.install 8 | 9 | task default: 'extension:specs' 10 | 11 | namespace :schema do 12 | desc 'Generates documentation from schema.graphql' 13 | task :generate_docs do 14 | doc_dir = "./lib/graphql_docs" 15 | GraphQLDocs.build( 16 | filename: "#{__dir__}/schema.graphql", 17 | output_dir: 'docs', 18 | base_url: '/solidus_graphql_api/docs', 19 | landing_pages: { index: "#{doc_dir}/landing_pages/index.md" }, 20 | delete_output: true 21 | ) 22 | end 23 | 24 | desc 'Dump the schema to JSON and IDL and generate docs' 25 | task :dump do 26 | setup_graphql_rake_tasks 27 | 28 | Rake::Task['graphql:schema:dump'].invoke 29 | Rake::Task['schema:generate_docs'].invoke 30 | end 31 | 32 | desc 'Dump the schema to IDL in schema.graphql and generate docs' 33 | task :idl do 34 | setup_graphql_rake_tasks 35 | 36 | Rake::Task['graphql:schema:idl'].invoke 37 | Rake::Task['schema:generate_docs'].invoke 38 | end 39 | 40 | desc 'Dump the schema to JSON in schema.json and generate docs' 41 | task :json do 42 | setup_graphql_rake_tasks 43 | 44 | Rake::Task['graphql:schema:json'].invoke 45 | Rake::Task['schema:generate_docs'].invoke 46 | end 47 | end 48 | 49 | private 50 | 51 | def setup_graphql_rake_tasks 52 | require 'graphql/rake_task' 53 | GraphQL::RakeTask.new(schema_name: 'SolidusGraphqlApi::Schema') 54 | 55 | Rake::Task['extension:test_app'].invoke if Dir['spec/dummy'].empty? 56 | 57 | require File.expand_path('spec/dummy/config/environment.rb', __dir__) 58 | end 59 | -------------------------------------------------------------------------------- /app/assets/javascripts/spree/backend/solidus_graphql_api.js: -------------------------------------------------------------------------------- 1 | // Placeholder manifest file. 2 | // the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/backend/all.js' -------------------------------------------------------------------------------- /app/assets/javascripts/spree/frontend/solidus_graphql_api.js: -------------------------------------------------------------------------------- 1 | // Placeholder manifest file. 2 | // the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/frontend/all.js' -------------------------------------------------------------------------------- /app/assets/stylesheets/spree/backend/solidus_graphql_api.css: -------------------------------------------------------------------------------- 1 | /* 2 | Placeholder manifest file. 3 | the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/backend/all.css' 4 | */ 5 | -------------------------------------------------------------------------------- /app/assets/stylesheets/spree/frontend/solidus_graphql_api.css: -------------------------------------------------------------------------------- 1 | /* 2 | Placeholder manifest file. 3 | the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/frontend/all.css' 4 | */ 5 | -------------------------------------------------------------------------------- /app/controllers/spree/graphql_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Spree 4 | class GraphqlController < ApplicationController 5 | include ActiveStorage::SetCurrent 6 | 7 | skip_before_action :verify_authenticity_token 8 | 9 | def execute 10 | render json: SolidusGraphqlApi::Schema.execute( 11 | params[:query], 12 | variables: ensure_hash(params[:variables]), 13 | context: SolidusGraphqlApi::Context.new(request: request).to_h, 14 | operation_name: params[:operationName] 15 | ) 16 | rescue StandardError => e 17 | raise e unless Rails.env.development? 18 | 19 | handle_error_in_development e 20 | end 21 | 22 | private 23 | 24 | # Handle form data, JSON body, or a blank value 25 | def ensure_hash(ambiguous_param) 26 | case ambiguous_param 27 | when String 28 | if ambiguous_param.present? 29 | ensure_hash(JSON.parse(ambiguous_param)) 30 | else 31 | {} 32 | end 33 | when Hash 34 | ambiguous_param 35 | when ActionController::Parameters 36 | ambiguous_param.permit! 37 | when nil 38 | {} 39 | else 40 | raise ArgumentError, "Unexpected parameter: #{ambiguous_param}" 41 | end 42 | end 43 | 44 | def handle_error_in_development(error) 45 | logger.error error.message 46 | logger.error error.backtrace.join("\n") 47 | 48 | render json: { error: { message: error.message, backtrace: error.backtrace }, data: {} }, status: 500 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # frozen_string_literal: true 4 | 5 | require "bundler/setup" 6 | require "solidus_graphql_api" 7 | 8 | # You can add fixtures and/or initialization code here to make experimenting 9 | # with your gem easier. You can also use a different console, if you like. 10 | $LOAD_PATH.unshift(*Dir["#{__dir__}/../app/*"]) 11 | 12 | # (If you use this, don't forget to add pry to your Gemfile!) 13 | # require "pry" 14 | # Pry.start 15 | 16 | require "irb" 17 | IRB.start(__FILE__) 18 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | if %w[g generate].include? ARGV.first 4 | exec "#{__dir__}/rails-engine", *ARGV 5 | else 6 | exec "#{__dir__}/rails-sandbox", *ARGV 7 | end 8 | -------------------------------------------------------------------------------- /bin/rails-engine: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails gems 3 | # installed from the root of your application. 4 | 5 | ENGINE_ROOT = File.expand_path('..', __dir__) 6 | ENGINE_PATH = File.expand_path('../lib/solidus_graphql_api/engine', __dir__) 7 | 8 | # Set up gems listed in the Gemfile. 9 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 10 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 11 | 12 | require 'rails/all' 13 | require 'rails/engine/commands' 14 | -------------------------------------------------------------------------------- /bin/rails-sandbox: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | app_root = 'sandbox' 4 | 5 | unless File.exist? "#{app_root}/bin/rails" 6 | warn 'Creating the sandbox app...' 7 | Dir.chdir "#{__dir__}/.." do 8 | system "#{__dir__}/sandbox" or begin 9 | warn 'Automatic creation of the sandbox app failed' 10 | exit 1 11 | end 12 | end 13 | end 14 | 15 | Dir.chdir app_root 16 | exec 'bin/rails', *ARGV 17 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "rubygems" 5 | require "bundler/setup" 6 | 7 | load Gem.bin_path("rake", "rake") 8 | -------------------------------------------------------------------------------- /bin/sandbox: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | case "$DB" in 6 | postgres|postgresql) 7 | RAILSDB="postgresql" 8 | ;; 9 | mysql) 10 | RAILSDB="mysql" 11 | ;; 12 | sqlite|'') 13 | RAILSDB="sqlite3" 14 | ;; 15 | *) 16 | echo "Invalid DB specified: $DB" 17 | exit 1 18 | ;; 19 | esac 20 | 21 | if [ ! -z $SOLIDUS_BRANCH ] 22 | then 23 | BRANCH=$SOLIDUS_BRANCH 24 | else 25 | BRANCH="main" 26 | fi 27 | 28 | extension_name="solidus_graphql_api" 29 | 30 | # Stay away from the bundler env of the containing extension. 31 | function unbundled { 32 | ruby -rbundler -e'b = proc {system *ARGV}; Bundler.respond_to?(:with_unbundled_env) ? Bundler.with_unbundled_env(&b) : Bundler.with_clean_env(&b)' -- $@ 33 | } 34 | 35 | rm -rf ./sandbox 36 | unbundled bundle exec rails new sandbox --database="$RAILSDB" \ 37 | --skip-bundle \ 38 | --skip-git \ 39 | --skip-keeps \ 40 | --skip-rc \ 41 | --skip-spring \ 42 | --skip-test \ 43 | --skip-javascript 44 | 45 | if [ ! -d "sandbox" ]; then 46 | echo 'sandbox rails application failed' 47 | exit 1 48 | fi 49 | 50 | cd ./sandbox 51 | cat <> Gemfile 52 | gem 'solidus', github: 'solidusio/solidus', branch: '$BRANCH' 53 | gem 'rails-i18n' 54 | gem 'solidus_i18n' 55 | 56 | gem '$extension_name', path: '..' 57 | 58 | group :test, :development do 59 | platforms :mri do 60 | gem 'pry-byebug' 61 | end 62 | end 63 | RUBY 64 | 65 | unbundled bundle install --gemfile Gemfile 66 | 67 | unbundled bundle exec rake db:drop db:create 68 | 69 | unbundled bundle exec rails generate solidus:install \ 70 | --auto-accept \ 71 | --authentication=devise \ 72 | --payment-method=none \ 73 | $@ 74 | 75 | unbundled bundle exec rails generate ${extension_name}:install 76 | 77 | echo 78 | echo "🚀 Sandbox app successfully created for $extension_name!" 79 | echo "🚀 Using $RAILSDB and Solidus $BRANCH" 80 | echo "🚀 Use 'export DB=[postgres|mysql|sqlite]' to control the DB adapter" 81 | echo "🚀 Use 'export SOLIDUS_BRANCH=' to control the Solidus version" 82 | echo "🚀 This app is intended for test purposes." 83 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | gem install bundler --conservative 7 | bundle update 8 | bin/rake clobber 9 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | activerecord: 3 | exceptions: 4 | not_found: Record not found 5 | unauthorized: 6 | default: You are not authorized to access this resource. 7 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | SolidusGraphqlApi::Engine.routes.draw do 4 | post '/', to: 'graphql#execute' 5 | end -------------------------------------------------------------------------------- /docs/assets/images/graphiql-headers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/images/graphiql-headers.png -------------------------------------------------------------------------------- /docs/assets/images/graphiql-variables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/images/graphiql-variables.png -------------------------------------------------------------------------------- /docs/assets/images/graphiql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/images/graphiql.png -------------------------------------------------------------------------------- /docs/assets/images/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/images/menu.png -------------------------------------------------------------------------------- /docs/assets/images/navbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/images/navbar.png -------------------------------------------------------------------------------- /docs/assets/webfonts/2C4B9D_B_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/webfonts/2C4B9D_B_0.eot -------------------------------------------------------------------------------- /docs/assets/webfonts/2C4B9D_B_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/webfonts/2C4B9D_B_0.ttf -------------------------------------------------------------------------------- /docs/assets/webfonts/2C4B9D_B_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/webfonts/2C4B9D_B_0.woff -------------------------------------------------------------------------------- /docs/assets/webfonts/2C4B9D_B_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/webfonts/2C4B9D_B_0.woff2 -------------------------------------------------------------------------------- /docs/assets/webfonts/2C4B9D_C_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/webfonts/2C4B9D_C_0.eot -------------------------------------------------------------------------------- /docs/assets/webfonts/2C4B9D_C_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/webfonts/2C4B9D_C_0.ttf -------------------------------------------------------------------------------- /docs/assets/webfonts/2C4B9D_C_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/webfonts/2C4B9D_C_0.woff -------------------------------------------------------------------------------- /docs/assets/webfonts/2C4B9D_C_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/webfonts/2C4B9D_C_0.woff2 -------------------------------------------------------------------------------- /docs/assets/webfonts/2C4B9D_D_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/webfonts/2C4B9D_D_0.eot -------------------------------------------------------------------------------- /docs/assets/webfonts/2C4B9D_D_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/webfonts/2C4B9D_D_0.ttf -------------------------------------------------------------------------------- /docs/assets/webfonts/2C4B9D_D_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/webfonts/2C4B9D_D_0.woff -------------------------------------------------------------------------------- /docs/assets/webfonts/2C4B9D_D_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/webfonts/2C4B9D_D_0.woff2 -------------------------------------------------------------------------------- /docs/assets/webfonts/2C4B9D_E_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/webfonts/2C4B9D_E_0.eot -------------------------------------------------------------------------------- /docs/assets/webfonts/2C4B9D_E_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/webfonts/2C4B9D_E_0.ttf -------------------------------------------------------------------------------- /docs/assets/webfonts/2C4B9D_E_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/webfonts/2C4B9D_E_0.woff -------------------------------------------------------------------------------- /docs/assets/webfonts/2C4B9D_E_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/docs/assets/webfonts/2C4B9D_E_0.woff2 -------------------------------------------------------------------------------- /lib/generators/solidus_graphql_api/install/install_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Generators 5 | class InstallGenerator < Rails::Generators::Base 6 | class_option :auto_run_migrations, type: :boolean, default: false 7 | source_root File.expand_path('templates', __dir__) 8 | argument :app_path, type: :string, default: Rails.root 9 | 10 | MOUNT_GRAPHQL_ENGINE = "mount SolidusGraphqlApi::Engine, at: '/graphql'" 11 | 12 | def copy_initializer 13 | template 'initializer.rb', 'config/initializers/solidus_graphql_api.rb' 14 | end 15 | 16 | def add_migrations 17 | run 'bin/rails railties:install:migrations FROM=solidus_graphql_api' 18 | end 19 | 20 | def run_migrations 21 | run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]')) # rubocop:disable Layout/LineLength 22 | if run_migrations 23 | run 'bin/rails db:migrate' 24 | else 25 | puts 'Skipping bin/rails db:migrate, don\'t forget to run it!' # rubocop:disable Rails/Output 26 | end 27 | end 28 | 29 | def install_routes 30 | if Pathname(app_path).join('config', 'routes.rb').read.include? MOUNT_GRAPHQL_ENGINE 31 | say_status :route_exist, MOUNT_GRAPHQL_ENGINE, :blue 32 | else 33 | route <<~RUBY 34 | # This line installs graphql's main route to execute queries 35 | 36 | mount SolidusGraphqlApi::Engine, at: '/graphql' 37 | RUBY 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/generators/solidus_graphql_api/install/templates/initializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | SolidusGraphqlApi.configure do |config| 4 | # TODO: Remember to change this with the actual preferences you have implemented! 5 | # config.sample_preference = 'sample_value' 6 | end 7 | -------------------------------------------------------------------------------- /lib/graphql_docs/landing_pages/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Solidus GraphQL documentation 3 | --- 4 | 5 | # Solidus GraphQL API 6 | 7 | This is the GraphQL documentation for the [Solidus](https://github.com/solidusio/solidus) ecommerce platform. 8 | 9 | ## Quick Reference 10 | 11 | - Solidus's GraphQL API endpoint is located at `/graphql`. 12 | - Check out GraphQL's [official documentation](https://graphql.org/) 13 | 14 | ## Need help? 15 | 16 | Join the [SolidusSlack#support-channel](http://slack.solidus.io/) and start a conversation. 17 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'graphql' 4 | require 'batch-loader' 5 | 6 | require 'solidus_graphql_api/version' 7 | require 'solidus_graphql_api/engine' 8 | require 'solidus_graphql_api/configuration' 9 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/batch_loader/belongs_to.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | class BatchLoader 5 | # A batch loader for +belongs_to+ associations. 6 | class BelongsTo < BatchLoader 7 | def load 8 | graphql_loader_for(object.send(reflection.foreign_key)) do |object_ids, loader| 9 | retrieve_records_for(object_ids).each do |record| 10 | loader.call(record.send(association_primary_key), record) 11 | end 12 | end 13 | end 14 | 15 | private 16 | 17 | def retrieve_records_for(object_ids) 18 | base_relation.where(association_primary_key => object_ids) 19 | end 20 | 21 | def association_primary_key 22 | if reflection.polymorphic? 23 | association_klass.primary_key 24 | else 25 | reflection.association_primary_key 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/batch_loader/has_many.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | class BatchLoader 5 | # A batch loader for +has_many+ associations. 6 | class HasMany < BatchLoader 7 | def load 8 | graphql_loader_for(object.id, default_value: []) do |object_ids, loader| 9 | retrieve_records_for(object_ids).each do |associated_record| 10 | loader.call(associated_record.send(reflection.foreign_key)) do |memo| 11 | memo << associated_record 12 | end 13 | end 14 | end 15 | end 16 | 17 | private 18 | 19 | def retrieve_records_for(object_ids) 20 | base_relation.where(reflection.foreign_key => object_ids) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/batch_loader/has_many_through.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | class BatchLoader 5 | # A batch loader for +has_many :through+ associations. 6 | class HasManyThrough < BatchLoader 7 | def load 8 | graphql_loader_for(object.id, default_value: []) do |object_ids, loader| 9 | records_by_parent = group_records_by_parent(retrieve_records_for(object_ids)) 10 | 11 | records_by_parent.each_pair do |parent_id, children| 12 | loader.call(parent_id) do |memo| 13 | memo.concat(children) 14 | end 15 | end 16 | end 17 | end 18 | 19 | private 20 | 21 | def retrieve_records_for(object_ids) 22 | through_reflection = reflection.through_reflection 23 | 24 | base_relation 25 | .joins(through_reflection.name) 26 | .where("#{through_reflection.table_name}.#{through_reflection.foreign_key}" => object_ids) 27 | .distinct 28 | end 29 | 30 | def group_records_by_parent(records) 31 | result = Hash.new { |h, k| h[k] = [] } 32 | 33 | records.each do |record| 34 | record.send(reflection.through_reflection.name).each do |parent| 35 | result[parent.send(reflection.through_reflection.foreign_key)] << record 36 | end 37 | end 38 | 39 | result 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/batch_loader/has_one.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | class BatchLoader 5 | # A batch loader for +has_one+ associations. 6 | class HasOne < BatchLoader 7 | def load 8 | graphql_loader_for(object.id) do |object_ids, loader| 9 | retrieve_records_for(object_ids).each do |record| 10 | loader.call(record.send(reflection.foreign_key), record) 11 | end 12 | end 13 | end 14 | 15 | private 16 | 17 | def retrieve_records_for(object_ids) 18 | base_relation.where(reflection.foreign_key => object_ids) 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/configuration.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | class Configuration 5 | attr_accessor :payment_sources 6 | 7 | def initialize 8 | @payment_sources = ['SolidusGraphqlApi::Types::CreditCard'] 9 | end 10 | end 11 | 12 | class << self 13 | def configuration 14 | @configuration ||= Configuration.new 15 | end 16 | 17 | alias config configuration 18 | 19 | def configure 20 | yield configuration 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | class Context 5 | AUTHORIZATION_HEADER = "Authorization" 6 | TOKEN_PATTERN = /^Bearer (?.*)/ 7 | 8 | attr_reader :request, :headers 9 | 10 | def initialize(request:) 11 | @request = request 12 | @headers = request.headers 13 | end 14 | 15 | def to_h 16 | { current_user: current_user, 17 | current_ability: current_ability, 18 | current_store: current_store, 19 | current_order: current_order, 20 | current_pricing_options: current_pricing_options, 21 | order_token: order_token } 22 | end 23 | 24 | def current_user 25 | @current_user ||= begin 26 | return nil if bearer_token.blank? 27 | 28 | Spree.user_class.find_by(spree_api_key: bearer_token) 29 | end 30 | end 31 | 32 | def current_ability 33 | @current_ability ||= Spree::Ability.new(current_user) 34 | end 35 | 36 | def order_token 37 | @order_token ||= headers["X-Spree-Order-Token"] 38 | end 39 | 40 | def current_store 41 | @current_store ||= Spree::Config.current_store_selector_class.new(request).store 42 | end 43 | 44 | def current_order 45 | return @current_order if instance_variable_defined?(:@current_order) 46 | 47 | @current_order = current_order_by_current_user || current_order_by_guest_token 48 | end 49 | 50 | def current_pricing_options 51 | @current_pricing_options ||= pricing_options_from_context 52 | end 53 | 54 | private 55 | 56 | def bearer_token 57 | @bearer_token ||= headers[AUTHORIZATION_HEADER].to_s.match(TOKEN_PATTERN) do |match_data| 58 | match_data[:token] 59 | end 60 | end 61 | 62 | def current_order_by_current_user 63 | current_user&.last_incomplete_spree_order(store: current_store) 64 | end 65 | 66 | def current_order_by_guest_token 67 | return if order_token.blank? 68 | 69 | incomplete_orders = Spree::Order.incomplete 70 | incomplete_orders = incomplete_orders.where(store: current_store) if current_store 71 | 72 | incomplete_orders.find_by(guest_token: order_token) 73 | end 74 | 75 | def pricing_options_from_context 76 | Spree::Config.pricing_options_class.new( 77 | currency: current_store&.default_currency.presence || Spree::Config[:currency], 78 | country_iso: current_store&.cart_tax_country_iso.presence 79 | ) 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'solidus_core' 4 | require 'solidus_support' 5 | 6 | module SolidusGraphqlApi 7 | class Engine < Rails::Engine 8 | include SolidusSupport::EngineExtensions 9 | 10 | isolate_namespace ::Spree 11 | 12 | engine_name 'solidus_graphql_api' 13 | 14 | # use rspec for tests 15 | config.generators do |g| 16 | g.test_framework :rspec 17 | end 18 | 19 | config.autoload_paths << File.expand_path('..', __dir__) 20 | 21 | initializer "solidus_graphql_api.setup_batch_loader_middleware" do |app| 22 | app.middleware.use BatchLoader::Middleware 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/lib/solidus_graphql_api/mutations/.keep -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/base_mutation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | class BaseMutation < GraphQL::Schema::RelayClassicMutation 6 | argument_class Types::Base::Argument 7 | field_class Types::Base::Field 8 | input_object_class Types::Base::InputObject 9 | object_class Types::Base::Object 10 | 11 | private 12 | 13 | def guest_token 14 | context[:order_token] 15 | end 16 | 17 | def current_order 18 | context[:current_order] 19 | end 20 | 21 | def current_user 22 | context[:current_user] 23 | end 24 | 25 | def current_ability 26 | context[:current_ability] 27 | end 28 | 29 | def current_store 30 | context[:current_store] 31 | end 32 | 33 | def user_errors(*path, errors) 34 | return [] if errors.empty? 35 | 36 | errors.map do |error| 37 | { 38 | path: ["input", *path].concat(error.attribute.to_s.camelize(:lower).split('.')), 39 | message: error.message, 40 | } 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/checkout/add_addresses_to_checkout.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | module Checkout 6 | class AddAddressesToCheckout < BaseMutation 7 | null true 8 | 9 | argument :billing_address, Types::InputObjects::AddressInput, required: true 10 | argument :shipping_address, Types::InputObjects::AddressInput, required: false 11 | argument :ship_to_billing_address, Boolean, required: false 12 | 13 | field :order, Types::Order, null: true 14 | field :errors, [Types::UserError], null: false 15 | 16 | def resolve(billing_address:, shipping_address: nil, ship_to_billing_address: false) 17 | current_order.update(state: :address) 18 | 19 | update_params = { 20 | bill_address: Spree::Address.new(billing_address.to_h), 21 | ship_address: Spree::Address.new(shipping_address.to_h), 22 | use_billing: ship_to_billing_address 23 | } 24 | 25 | if Spree::OrderUpdateAttributes.new(current_order, update_params).apply 26 | current_order.recalculate 27 | errors = [] 28 | else 29 | errors = current_order.errors 30 | end 31 | 32 | { errors: user_errors('order', errors), order: current_order } 33 | end 34 | 35 | def ready?(*) 36 | current_ability.authorize!(:update, current_order, guest_token) 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/checkout/add_payment_to_checkout.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | module Checkout 6 | class AddPaymentToCheckout < BaseMutation 7 | null true 8 | 9 | argument :payment_method_id, ID, required: true, loads: Types::PaymentMethod 10 | argument :amount, Float, required: false 11 | argument :source, Types::Json, required: false 12 | 13 | field :order, Types::Order, null: true 14 | field :errors, [Types::UserError], null: false 15 | 16 | def resolve(payment_method:, source:, amount: nil) 17 | current_order.update(state: :payment) 18 | 19 | update_params = { 20 | payments_attributes: [{ 21 | payment_method_id: payment_method.id, 22 | amount: amount || current_order.total, 23 | source_attributes: source 24 | }] 25 | } 26 | 27 | if Spree::OrderUpdateAttributes.new(current_order, update_params).apply 28 | current_order.recalculate 29 | errors = [] 30 | else 31 | errors = current_order.errors 32 | end 33 | 34 | { errors: user_errors('order', errors), order: current_order } 35 | end 36 | 37 | private 38 | 39 | def ready?(*) 40 | current_ability.authorize!(:update, current_order, guest_token) 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/checkout/add_to_cart.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | module Checkout 6 | class AddToCart < BaseMutation 7 | null true 8 | 9 | argument :variant_id, ID, required: true, loads: Types::Variant 10 | argument :quantity, Integer, required: true 11 | 12 | field :order, Types::Order, null: true 13 | field :errors, [Types::UserError], null: false 14 | 15 | def resolve(variant:, quantity:) 16 | line_item = current_order.contents.add(variant, quantity) 17 | 18 | { 19 | order: current_order, 20 | errors: user_errors("line_item", line_item.errors) 21 | } 22 | end 23 | 24 | def ready?(*) 25 | current_ability.authorize!(:update, current_order, guest_token) 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/checkout/advance_checkout.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | module Checkout 6 | class AdvanceCheckout < BaseMutation 7 | null true 8 | 9 | field :order, Types::Order, null: true 10 | field :errors, [Types::UserError], null: false 11 | 12 | def resolve 13 | current_order.contents.advance 14 | 15 | { 16 | order: current_order.reload, 17 | errors: user_errors("order", current_order.errors) 18 | } 19 | end 20 | 21 | def ready?(*) 22 | current_ability.authorize!(:update, current_order, guest_token) 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/checkout/apply_coupon_code.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | module Checkout 6 | class ApplyCouponCode < BaseMutation 7 | null true 8 | 9 | argument :coupon_code, String, required: true 10 | 11 | field :order, Types::Order, null: true 12 | field :errors, [Types::UserError], null: false 13 | 14 | def resolve(coupon_code:) 15 | current_order.coupon_code = coupon_code 16 | handler = Spree::PromotionHandler::Coupon.new(current_order).apply 17 | 18 | current_order.errors.add(:coupon_code, handler.error) unless handler.successful? 19 | 20 | { 21 | order: current_order, 22 | errors: user_errors("order", current_order.errors) 23 | } 24 | end 25 | 26 | def ready?(*) 27 | current_ability.authorize!(:update, current_order, guest_token) 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/checkout/complete_checkout.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | module Checkout 6 | class CompleteCheckout < BaseMutation 7 | null true 8 | 9 | field :order, Types::Order, null: true 10 | field :errors, [Types::UserError], null: false 11 | 12 | def resolve 13 | current_order.complete 14 | 15 | { 16 | order: current_order.reload, 17 | errors: user_errors("order", current_order.errors) 18 | } 19 | end 20 | 21 | def authorized? 22 | current_order.can_complete? 23 | end 24 | 25 | def ready?(*) 26 | current_ability.authorize!(:update, current_order, guest_token) 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/checkout/create_order.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | module Checkout 6 | class CreateOrder < BaseMutation 7 | null true 8 | 9 | field :order, Types::Order, null: true 10 | field :errors, [Types::UserError], null: false 11 | 12 | def resolve 13 | order = Spree::Order.create!(user: current_user, store: current_store) 14 | 15 | { 16 | order: order, 17 | errors: user_errors("order", order.errors) 18 | } 19 | end 20 | 21 | def ready?(*) 22 | current_ability.authorize!(:create, Spree::Order) 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/checkout/empty_cart.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | module Checkout 6 | class EmptyCart < BaseMutation 7 | null true 8 | 9 | field :order, Types::Order, null: true 10 | field :errors, [Types::UserError], null: false 11 | 12 | def resolve 13 | current_order.empty! 14 | 15 | current_order.restart_checkout_flow 16 | 17 | { 18 | order: current_order, 19 | errors: user_errors("order", current_order.errors) 20 | } 21 | end 22 | 23 | def ready?(*) 24 | current_ability.authorize!(:update, current_order, guest_token) 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/checkout/next_checkout_state.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | module Checkout 6 | class NextCheckoutState < BaseMutation 7 | null true 8 | 9 | field :order, Types::Order, null: true 10 | field :errors, [Types::UserError], null: false 11 | 12 | def resolve 13 | current_order.next 14 | 15 | { 16 | order: current_order.reload, 17 | errors: user_errors("order", current_order.errors) 18 | } 19 | end 20 | 21 | def ready?(*) 22 | current_ability.authorize!(:update, current_order, guest_token) 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/checkout/remove_from_cart.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | module Checkout 6 | class RemoveFromCart < BaseMutation 7 | null true 8 | 9 | argument :line_item_id, ID, required: true, loads: Types::LineItem 10 | 11 | field :order, Types::Order, null: true 12 | field :errors, [Types::UserError], null: false 13 | 14 | def resolve(line_item:) 15 | line_item = current_order.contents.remove_line_item(line_item) 16 | 17 | { 18 | order: current_order, 19 | errors: user_errors("line_item", line_item.errors) 20 | } 21 | end 22 | 23 | def authorized?(line_item:) 24 | current_order.line_items.find(line_item.id) 25 | end 26 | 27 | def ready?(*) 28 | current_ability.authorize!(:update, current_order, guest_token) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/checkout/select_shipping_rate.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | module Checkout 6 | class SelectShippingRate < BaseMutation 7 | null true 8 | 9 | argument :shipping_rate_id, ID, required: true, loads: Types::ShippingRate 10 | 11 | field :order, Types::Order, null: true 12 | field :errors, [Types::UserError], null: false 13 | 14 | def resolve(shipping_rate:) 15 | current_order.update(state: :delivery) 16 | 17 | update_params = { 18 | shipments_attributes: { 19 | id: shipping_rate.shipment_id, 20 | selected_shipping_rate_id: shipping_rate.id 21 | } 22 | } 23 | 24 | if Spree::OrderUpdateAttributes.new(current_order, update_params).apply 25 | current_order.recalculate 26 | errors = [] 27 | else 28 | errors = current_order.errors 29 | end 30 | 31 | { errors: user_errors('order', errors), order: current_order } 32 | end 33 | 34 | def ready?(*) 35 | current_ability.authorize!(:update, current_order, guest_token) 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/checkout/set_order_email.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | module Checkout 6 | class SetOrderEmail < BaseMutation 7 | null true 8 | 9 | argument :email, String, required: true 10 | 11 | field :order, Types::Order, null: true 12 | field :errors, [Types::UserError], null: false 13 | 14 | def resolve(email:) 15 | current_order.update(email: email) 16 | 17 | { 18 | order: current_order, 19 | errors: user_errors("order", current_order.errors) 20 | } 21 | end 22 | 23 | def ready?(*) 24 | current_ability.authorize!(:update, current_order, guest_token) 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/checkout/update_cart_quantity.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | module Checkout 6 | class UpdateCartQuantity < BaseMutation 7 | null true 8 | 9 | argument :line_item_id, ID, required: true, loads: Types::LineItem 10 | argument :quantity, Integer, required: true 11 | 12 | field :order, Types::Order, null: true 13 | field :errors, [Types::UserError], null: false 14 | 15 | def resolve(line_item:, quantity:) 16 | update_params = { 17 | line_items_attributes: { 18 | id: line_item.id, 19 | quantity: quantity 20 | } 21 | } 22 | 23 | current_order.contents.update_cart(update_params) 24 | 25 | { 26 | order: current_order, 27 | errors: user_errors("line_item", line_item.errors) 28 | } 29 | end 30 | 31 | def authorized?(line_item:, **) 32 | current_order.line_items.find(line_item.id) 33 | end 34 | 35 | def ready?(*) 36 | current_ability.authorize!(:update, current_order, guest_token) 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/user/mark_default_ship_address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | module User 6 | class MarkDefaultShipAddress < BaseMutation 7 | null true 8 | 9 | argument :address_id, ID, required: true, loads: Types::Address 10 | 11 | field :user, Types::User, null: true 12 | 13 | def resolve(address:) 14 | current_user.mark_default_ship_address(address) 15 | 16 | { user: current_user.reload } 17 | end 18 | 19 | def ready?(*) 20 | return true if current_user.present? 21 | 22 | raise CanCan::AccessDenied 23 | end 24 | 25 | def authorized?(address:) 26 | current_user.user_addresses.find_by!(address_id: address.id) 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/user/remove_from_address_book.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | module User 6 | class RemoveFromAddressBook < BaseMutation 7 | null true 8 | 9 | argument :address_id, ID, required: true, loads: Types::Address 10 | 11 | field :user, Types::User, null: true 12 | 13 | def resolve(address:) 14 | raise CanCan::AccessDenied unless current_user.remove_from_address_book(address.id) 15 | 16 | { user: current_user.reload } 17 | end 18 | 19 | def ready?(*) 20 | current_ability.authorize!(:remove_from_address_book, current_user) 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/mutations/user/save_in_address_book.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Mutations 5 | module User 6 | class SaveInAddressBook < BaseMutation 7 | class AddressTypeInput < Types::Base::Enum 8 | value "shipping" 9 | value "billing" 10 | end 11 | 12 | null true 13 | 14 | argument :address, Types::InputObjects::AddressInput, required: true 15 | argument :default, Boolean, required: false 16 | argument :address_type, AddressTypeInput, required: false, default_value: "shipping" 17 | 18 | field :user, Types::User, null: true 19 | field :errors, [Types::UserError], null: false 20 | 21 | def resolve(address:, address_type:, default: false) 22 | address = current_user.save_in_address_book(address, default, address_type.to_sym) 23 | 24 | { 25 | user: current_user, 26 | errors: user_errors("address", address.errors) 27 | } 28 | end 29 | 30 | def ready?(*) 31 | current_ability.authorize!(:save_in_address_book, current_user) 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/address/country_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Address 6 | class CountryQuery 7 | attr_reader :address 8 | 9 | def initialize(address:) 10 | @address = address 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(address, :country) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/address/state_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Address 6 | class StateQuery 7 | attr_reader :address 8 | 9 | def initialize(address:) 10 | @address = address 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(address, :state) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/completed_orders_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | class CompletedOrdersQuery 6 | attr_reader :user 7 | 8 | def initialize(user:) 9 | @user = user 10 | end 11 | 12 | def call 13 | return [] unless user 14 | 15 | user.orders.complete.order :id 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/countries_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | class CountriesQuery 6 | def call 7 | Spree::Country.all 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/country/states_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Country 6 | class StatesQuery 7 | attr_reader :country 8 | 9 | def initialize(country:) 10 | @country = country 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(country, :states) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/line_item/variant_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module LineItem 6 | class VariantQuery 7 | attr_reader :line_item 8 | 9 | def initialize(line_item:) 10 | @line_item = line_item 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(line_item, :variant) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/option_type/option_values_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module OptionType 6 | class OptionValuesQuery 7 | attr_reader :option_type 8 | 9 | def initialize(option_type:) 10 | @option_type = option_type 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(option_type, :option_values) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/option_value/option_type_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module OptionValue 6 | class OptionTypeQuery 7 | attr_reader :option_value 8 | 9 | def initialize(option_value:) 10 | @option_value = option_value 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(option_value, :option_type) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/order/adjustments_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Order 6 | class AdjustmentsQuery 7 | attr_reader :order 8 | 9 | def initialize(order:) 10 | @order = order 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(order, :all_adjustments) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/order/billing_address_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Order 6 | class BillingAddressQuery 7 | attr_reader :order 8 | 9 | def initialize(order:) 10 | @order = order 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(order, :bill_address) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/order/line_items_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Order 6 | class LineItemsQuery 7 | attr_reader :order 8 | 9 | def initialize(order:) 10 | @order = order 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(order, :line_items) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/order/payments_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Order 6 | class PaymentsQuery 7 | attr_reader :order 8 | 9 | def initialize(order:) 10 | @order = order 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(order, :payments) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/order/shipments_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Order 6 | class ShipmentsQuery 7 | attr_reader :order 8 | 9 | def initialize(order:) 10 | @order = order 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(order, :shipments) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/order/shipping_address_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Order 6 | class ShippingAddressQuery 7 | attr_reader :order 8 | 9 | def initialize(order:) 10 | @order = order 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(order, :ship_address) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/product/master_variant_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Product 6 | class MasterVariantQuery 7 | attr_reader :product 8 | 9 | def initialize(product:) 10 | @product = product 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(product, :master) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/product/option_types_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Product 6 | class OptionTypesQuery 7 | attr_reader :product 8 | 9 | def initialize(product:) 10 | @product = product 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(product, :option_types) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/product/product_properties_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Product 6 | class ProductPropertiesQuery 7 | attr_reader :product 8 | 9 | def initialize(product:) 10 | @product = product 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(product, :product_properties) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/product/variants_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Product 6 | class VariantsQuery 7 | attr_reader :product 8 | 9 | def initialize(product:) 10 | @product = product 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(product, :variants) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/product_by_slug_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | class ProductBySlugQuery 6 | def call(slug:) 7 | Spree::Product.find_by slug: slug 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/product_property/property_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module ProductProperty 6 | class PropertyQuery 7 | attr_reader :product_property 8 | 9 | def initialize(product_property:) 10 | @product_property = product_property 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(product_property, :property) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/products_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | class ProductsQuery 6 | attr_reader :user, :pricing_options 7 | 8 | def initialize(user:, pricing_options:) 9 | @user = user 10 | @pricing_options = pricing_options 11 | end 12 | 13 | def call(query: {}) 14 | Spree::Config.searcher_class.new(build_query(query)).tap do |searcher| 15 | searcher.current_user = user 16 | searcher.pricing_options = pricing_options 17 | end.retrieve_products.except(:limit, :offset) 18 | end 19 | 20 | private 21 | 22 | def build_query(query) 23 | query.to_h.tap do |q| 24 | q[:taxon] = q[:taxon].id unless q[:taxon].nil? 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/shipment/shipping_rates_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Shipment 6 | class ShippingRatesQuery 7 | attr_reader :shipment 8 | 9 | def initialize(shipment:) 10 | @shipment = shipment 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(shipment, :shipping_rates) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/shipping_rate/shipping_method_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module ShippingRate 6 | class ShippingMethodQuery 7 | attr_reader :shipping_rate 8 | 9 | def initialize(shipping_rate:) 10 | @shipping_rate = shipping_rate 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(shipping_rate, :shipping_method) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/taxon/children_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Taxon 6 | class ChildrenQuery 7 | attr_reader :taxon 8 | 9 | def initialize(taxon:) 10 | @taxon = taxon 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(taxon, :children, scope: Spree::Taxon.order(:id)) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/taxon/parent_taxon_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Taxon 6 | class ParentTaxonQuery 7 | attr_reader :taxon 8 | 9 | def initialize(taxon:) 10 | @taxon = taxon 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(taxon, :parent) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/taxonomies_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | class TaxonomiesQuery 6 | def call 7 | Spree::Taxonomy.all 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/taxonomy/root_taxon_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Taxonomy 6 | class RootTaxonQuery 7 | attr_reader :taxonomy 8 | 9 | def initialize(taxonomy:) 10 | @taxonomy = taxonomy 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(taxonomy, :root) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/taxonomy/taxons_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Taxonomy 6 | class TaxonsQuery 7 | attr_reader :taxonomy 8 | 9 | def initialize(taxonomy:) 10 | @taxonomy = taxonomy 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(taxonomy, :taxons, scope: Spree::Taxon.order(:id)) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/variant/default_price_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Variant 6 | class DefaultPriceQuery 7 | attr_reader :variant 8 | 9 | def initialize(variant:) 10 | @variant = variant 11 | end 12 | 13 | def call 14 | variant.default_price 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/variant/images_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Variant 6 | class ImagesQuery 7 | attr_reader :variant 8 | 9 | def initialize(variant:) 10 | @variant = variant 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(variant, :images) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/variant/option_values_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Variant 6 | class OptionValuesQuery 7 | attr_reader :variant 8 | 9 | def initialize(variant:) 10 | @variant = variant 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(variant, :option_values) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/queries/variant/prices_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Queries 5 | module Variant 6 | class PricesQuery 7 | attr_reader :variant 8 | 9 | def initialize(variant:) 10 | @variant = variant 11 | end 12 | 13 | def call 14 | SolidusGraphqlApi::BatchLoader.for(variant, :prices) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | class Schema < GraphQL::Schema 5 | query Types::Query 6 | mutation Types::Mutation 7 | 8 | use ::BatchLoader::GraphQL 9 | 10 | rescue_from CanCan::AccessDenied do |exception| 11 | raise GraphQL::ExecutionError, exception.message 12 | end 13 | 14 | rescue_from ActiveRecord::RecordNotFound do 15 | raise GraphQL::ExecutionError, I18n.t(:'activerecord.exceptions.not_found') 16 | end 17 | 18 | # Relay Object Identification: 19 | class << self 20 | # Return a string UUID for object 21 | def id_from_object(object, _type_definition, _query_ctx) 22 | GraphQL::Schema::UniqueWithinType.encode(object.class.name, object.id) 23 | end 24 | 25 | # Given a string UUID, find the object 26 | def object_from_id(id, _query_ctx) 27 | class_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id) 28 | 29 | ::Object.const_get(class_name).find(item_id) 30 | end 31 | 32 | # Object Resolution 33 | def resolve_type(_type_definition, object, _query_ctx) 34 | class_name = object.is_a?(Spree::PaymentMethod) ? "PaymentMethod" : object.class.name.demodulize 35 | 36 | Types.const_get(class_name) 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/testing_support/factories.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'solidus_graphql_api/testing_support/factories/address_factory' 4 | require 'solidus_graphql_api/testing_support/factories/country_factory' 5 | require 'solidus_graphql_api/testing_support/factories/store_factory' 6 | require 'solidus_graphql_api/testing_support/factories/taxonomy_factory' 7 | 8 | FactoryBot.define do 9 | end 10 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/testing_support/factories/address_factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.modify do 4 | factory :address do 5 | name { 'John Von Doe' } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/testing_support/factories/country_factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.modify do 4 | factory :country do 5 | trait :with_states do 6 | after :create do |country| 7 | create(:state, id: 1, country: country) 8 | create(:state, id: 2, country: country, state_code: 'CA') 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/testing_support/factories/store_factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.modify do 4 | factory :store do 5 | trait :with_defaults do 6 | cart_tax_country_iso { 'US' } 7 | code { 'solidus' } 8 | default_currency { 'USD' } 9 | meta_description { 'store description' } 10 | meta_keywords { 'store, metaKeywords' } 11 | name { 'Solidus Test Store' } 12 | seo_title { 'Store Title' } 13 | url { 'www.example.com' } 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/testing_support/factories/taxonomy_factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.modify do 4 | factory :taxonomy do 5 | transient do 6 | root_taxon_id {} 7 | end 8 | 9 | after :create do |taxonomy, options| 10 | if options.root_taxon_id.present? && taxonomy.root.id != options.root_taxon_id 11 | taxonomy.root.update(id: options.root_taxon_id) 12 | end 13 | end 14 | 15 | trait :with_taxon_meta do 16 | after :create do |taxonomy| 17 | taxonomy.root.update( 18 | description: 'Brand description', 19 | meta_description: 'Brand meta description', 20 | meta_keywords: 'Brand meta keywords', 21 | meta_title: 'Brand meta title', 22 | ) 23 | end 24 | end 25 | 26 | trait :with_root_icon do 27 | after :create do |taxonomy| 28 | taxonomy.root.update(icon: Spree::Core::Engine.root.join('lib', 29 | 'spree', 30 | 'testing_support', 31 | 'fixtures', 32 | 'blank.jpg').open) 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidusio/solidus_graphql_api/106dfe1d816568dd7fb0292a9a193f7543aaa4e2/lib/solidus_graphql_api/types/.keep -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class Address < Base::RelayNode 6 | description 'Address.' 7 | 8 | field :address1, String, null: false 9 | field :address2, String, null: true 10 | field :alternative_phone, String, null: true 11 | field :city, String, null: false 12 | field :company, String, null: true 13 | field :country, Country, null: false 14 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 15 | field :name, String, null: false 16 | field :phone, String, null: false 17 | field :state_name, String, null: true 18 | field :state, State, null: true 19 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 20 | field :zipcode, String, null: false 21 | 22 | def state 23 | Queries::Address::StateQuery.new(address: object).call 24 | end 25 | 26 | def country 27 | Queries::Address::CountryQuery.new(address: object).call 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/base/argument.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | module Base 6 | class Argument < GraphQL::Schema::Argument 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/base/enum.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | module Base 6 | class Enum < GraphQL::Schema::Enum 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/base/field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | module Base 6 | class Field < GraphQL::Schema::Field 7 | def resolve_field(obj, args, ctx) 8 | resolve(obj, args, ctx) 9 | end 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/base/input_object.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | module Base 6 | class InputObject < GraphQL::Schema::InputObject 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/base/interface.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | module Base 6 | module Interface 7 | include GraphQL::Schema::Interface 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/base/object.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | module Base 6 | class Object < GraphQL::Schema::Object 7 | class << self 8 | # Removes a field from this schema. 9 | # 10 | # @param field [Symbol] the field to remove 11 | # 12 | # @example Removing a field in a decorator 13 | # Spree::Graphql::Types::Variant.remove_field :prices 14 | def remove_field(field) 15 | unless own_fields.key?(field.to_s) 16 | raise ArgumentError, "Field `#{field}` is not defined" 17 | end 18 | 19 | own_fields.delete(field.to_s) 20 | end 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/base/relay_node.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | module Base 6 | class RelayNode < Base::Object 7 | implements GraphQL::Types::Relay::Node 8 | 9 | global_id_field :id 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/base/scalar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | module Base 6 | class Scalar < GraphQL::Schema::Scalar 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/base/union.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | module Base 6 | class Union < GraphQL::Schema::Union 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/country.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class Country < Base::RelayNode 6 | description 'Country.' 7 | 8 | field :iso_name, String, null: false 9 | field :iso, String, null: false 10 | field :iso3, String, null: false 11 | field :name, String, null: false 12 | field :numcode, Integer, null: false 13 | field :states, State.connection_type, null: false 14 | field :states_required, Boolean, null: false 15 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 16 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 17 | 18 | def states 19 | Queries::Country::StatesQuery.new(country: object).call 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/credit_card.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class CreditCard < Base::RelayNode 6 | implements Types::Interfaces::PaymentSource 7 | 8 | description 'Credit Card.' 9 | 10 | field :address, Address, null: false 11 | field :cc_type, String, null: true 12 | field :last_digits, String, null: false 13 | field :month, String, null: false 14 | field :name, String, null: false 15 | field :year, String, null: false 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/currency.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class Currency < Base::RelayNode 6 | description 'Currency.' 7 | 8 | field :html_entity, String, null: false 9 | field :iso_code, String, null: false 10 | field :name, String, null: false 11 | field :symbol, String, null: false 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/image.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class Image < Base::RelayNode 6 | description 'Image.' 7 | 8 | field :alt, String, null: true 9 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 10 | field :filename, String, null: false 11 | field :large_url, String, null: false 12 | field :mini_url, String, null: false 13 | field :position, Integer, null: false 14 | field :product_url, String, null: false 15 | field :small_url, String, null: false 16 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 17 | 18 | def large_url; object.url(:large) end 19 | 20 | def mini_url; object.url(:mini) end 21 | 22 | def product_url; object.url(:product) end 23 | 24 | def small_url; object.url(:small) end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/input_objects/address_input.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | module InputObjects 6 | class AddressInput < Base::InputObject 7 | description "Address." 8 | 9 | argument :address1, String, required: true 10 | argument :address2, String, required: false 11 | argument :alternative_phone, String, required: false 12 | argument :city, String, required: true 13 | argument :company, String, required: false 14 | argument :country_id, ID, required: true, loads: Types::Country 15 | argument :name, String, required: true 16 | argument :phone, String, required: true 17 | argument :state_name, String, required: false 18 | argument :state_id, ID, required: false, loads: Types::State 19 | argument :zipcode, String, required: true 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/input_objects/products_query_input.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | module InputObjects 6 | class ProductsQueryInput < Base::InputObject 7 | description "Params for searching products." 8 | 9 | argument :taxon, ID, "Taxon", required: false, loads: Types::Taxon 10 | argument :keywords, String, "Keywords", required: false 11 | argument :search, Types::Json, "Search", required: false 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/interfaces/adjustment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | module Interfaces 6 | module Adjustment 7 | include Types::Base::Interface 8 | 9 | description "Adjustment." 10 | 11 | orphan_types Types::TaxAdjustment, Types::PromotionAdjustment 12 | 13 | field :amount, String, null: false 14 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 15 | field :eligible, Boolean, null: false 16 | field :label, String, null: false 17 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 18 | 19 | definition_methods do 20 | def resolve_type(object, _context) 21 | case object.source_type 22 | when Spree::TaxRate.to_s 23 | Types::TaxAdjustment 24 | when Spree::PromotionAction.to_s 25 | Types::PromotionAdjustment 26 | end 27 | end 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/interfaces/payment_source.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | module Interfaces 6 | module PaymentSource 7 | include Types::Base::Interface 8 | 9 | orphan_types(*SolidusGraphqlApi.configuration.payment_sources.map(&:constantize)) 10 | 11 | description "Payment Source." 12 | 13 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 14 | field :payment_method, Types::PaymentMethod, null: false 15 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/json.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class Json < GraphQL::Types::JSON 6 | def self.coerce_input(value, ctx) 7 | value = value.is_a?(ActionController::Parameters) ? value.permit!.to_h : value.to_h 8 | value.each do |key, field| 9 | case field 10 | when String 11 | value[key] = decode(field) 12 | when Array 13 | value[key] = decode_array(field, ctx) 14 | when Hash, ActionController::Parameters 15 | value[key] = coerce_input(field, ctx) 16 | end 17 | end 18 | value 19 | end 20 | 21 | def self.decode(value) 22 | class_name, item_id = decode_if_relay_id(value) 23 | 24 | return value unless class_exists?(class_name) 25 | 26 | to_i_or_nil(item_id) || value 27 | end 28 | 29 | def self.decode_array(array, ctx) 30 | array.map do |value| 31 | case value 32 | when Hash, ActionController::Parameters 33 | coerce_input(value, ctx) 34 | when String 35 | decode(value) 36 | else 37 | value 38 | end 39 | end 40 | end 41 | 42 | def self.decode_if_relay_id(value) 43 | GraphQL::Schema::UniqueWithinType.decode(value) 44 | # As of graphql v1.10.8 a GraphQL::ExecutionError is raised instead of an ArgumentError 45 | rescue ArgumentError, GraphQL::ExecutionError 46 | [nil, nil] 47 | end 48 | 49 | def self.class_exists?(value) 50 | return false if value.nil? 51 | 52 | Object.const_defined?(value) 53 | rescue NameError 54 | false 55 | end 56 | 57 | def self.to_i_or_nil(value) 58 | return value if value.nil? 59 | 60 | Integer(value) 61 | rescue ArgumentError 62 | nil 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/line_item.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class LineItem < Base::RelayNode 6 | description 'Line item.' 7 | 8 | field :additional_tax_total, Float, null: false 9 | field :adjustment_total, Float, null: false 10 | field :amount, Float, null: false 11 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 12 | field :currency, String, null: false 13 | field :has_sufficient_stock, Boolean, null: false, method: :sufficient_stock? 14 | field :included_tax_total, Float, null: false 15 | field :price, Float, null: false 16 | field :promo_total, Float, null: false 17 | field :quantity, Integer, null: false 18 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 19 | field :variant, Variant, null: false 20 | 21 | def variant 22 | Queries::LineItem::VariantQuery.new(line_item: object).call 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/manifest_item.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class ManifestItem < Base::Object 6 | description 'Shipping Manifest Item.' 7 | 8 | field :variant, Types::Variant, null: false 9 | field :quantity, Integer, null: false 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/mutation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class Mutation < Base::Object 6 | field :save_in_address_book, mutation: Mutations::User::SaveInAddressBook 7 | field :remove_from_address_book, mutation: Mutations::User::RemoveFromAddressBook 8 | field :mark_default_ship_address, mutation: Mutations::User::MarkDefaultShipAddress 9 | field :add_addresses_to_checkout, mutation: Mutations::Checkout::AddAddressesToCheckout 10 | field :select_shipping_rate, mutation: Mutations::Checkout::SelectShippingRate 11 | field :add_payment_to_checkout, mutation: Mutations::Checkout::AddPaymentToCheckout 12 | field :next_checkout_state, mutation: Mutations::Checkout::NextCheckoutState 13 | field :advance_checkout, mutation: Mutations::Checkout::AdvanceCheckout 14 | field :complete_checkout, mutation: Mutations::Checkout::CompleteCheckout 15 | field :create_order, mutation: Mutations::Checkout::CreateOrder 16 | field :add_to_cart, mutation: Mutations::Checkout::AddToCart 17 | field :remove_from_cart, mutation: Mutations::Checkout::RemoveFromCart 18 | field :empty_cart, mutation: Mutations::Checkout::EmptyCart 19 | field :update_cart_quantity, mutation: Mutations::Checkout::UpdateCartQuantity 20 | field :apply_coupon_code, mutation: Mutations::Checkout::ApplyCouponCode 21 | field :set_order_email, mutation: Mutations::Checkout::SetOrderEmail 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/option_type.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class OptionType < Base::RelayNode 6 | graphql_name 'OptionType' 7 | 8 | description 'OptionType Type.' 9 | 10 | field :name, String, null: false 11 | field :presentation, String, null: false 12 | field :position, Int, null: false 13 | field :option_values, OptionValue.connection_type, null: false 14 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 15 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 16 | 17 | def option_values 18 | Queries::OptionType::OptionValuesQuery.new(option_type: object).call 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/option_value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class OptionValue < Base::RelayNode 6 | description 'OptionValue.' 7 | 8 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 9 | field :name, String, null: false 10 | field :position, String, null: false 11 | field :presentation, String, null: false 12 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 13 | field :option_type, OptionType, null: false 14 | 15 | def option_type 16 | Queries::OptionValue::OptionTypeQuery.new(option_value: object).call 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/order.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class Order < Base::RelayNode 6 | description 'Order.' 7 | 8 | field :additional_tax_total, String, null: false 9 | field :adjustment_total, String, null: false 10 | field :adjustments, Types::Interfaces::Adjustment.connection_type, null: false 11 | field :approved_at, GraphQL::Types::ISO8601DateTime, null: true 12 | field :billing_address, Address, null: true 13 | field :canceled_at, GraphQL::Types::ISO8601DateTime, null: true 14 | field :completed_at, GraphQL::Types::ISO8601DateTime, null: true 15 | field :confirmation_delivered, Boolean, null: false 16 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 17 | field :currency, String, null: false 18 | field :email, String, null: true 19 | field :guest_token, String, null: true 20 | field :included_tax_total, String, null: false 21 | field :item_total, String, null: false 22 | field :line_items, LineItem.connection_type, null: false 23 | field :number, String, null: false 24 | field :available_payment_methods, [PaymentMethod], null: false 25 | field :payment_state, String, null: false 26 | field :payment_total, String, null: false 27 | field :payments, [Payment], null: false 28 | field :promo_total, String, null: false 29 | field :shipment_state, String, null: false 30 | field :shipment_total, String, null: false 31 | field :shipments, Shipment.connection_type, null: false 32 | field :shipping_address, Address, null: true 33 | field :special_instructions, String, null: true 34 | field :state, String, null: false 35 | field :total, String, null: false 36 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 37 | 38 | def adjustments 39 | Queries::Order::AdjustmentsQuery.new(order: object).call 40 | end 41 | 42 | def billing_address 43 | Queries::Order::BillingAddressQuery.new(order: object).call 44 | end 45 | 46 | def line_items 47 | Queries::Order::LineItemsQuery.new(order: object).call 48 | end 49 | 50 | def payments 51 | Queries::Order::PaymentsQuery.new(order: object).call 52 | end 53 | 54 | def shipments 55 | Queries::Order::ShipmentsQuery.new(order: object).call 56 | end 57 | 58 | def shipping_address 59 | Queries::Order::ShippingAddressQuery.new(order: object).call 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/payment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class Payment < Base::RelayNode 6 | description 'Payment.' 7 | 8 | field :amount, String, null: true 9 | field :state, String, null: false 10 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 11 | field :payment_source, Types::Interfaces::PaymentSource, null: true 12 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/payment_method.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class PaymentMethod < Base::RelayNode 6 | description 'Payment Method.' 7 | 8 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 9 | field :description, String, null: true 10 | field :name, String, null: false 11 | field :position, String, null: false 12 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/price.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class Price < Base::RelayNode 6 | description 'Price.' 7 | 8 | field :amount, String, null: false 9 | field :country_iso, String, null: true 10 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 11 | field :currency, Currency, null: false 12 | field :display_amount, String, null: false 13 | field :display_country, String, null: false 14 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 15 | 16 | def currency 17 | object.display_amount.currency 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/product.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class Product < Base::RelayNode 6 | description 'Product.' 7 | 8 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 9 | field :description, String, null: true 10 | field :master_variant, Variant, null: false 11 | field :meta_description, String, null: true 12 | field :meta_keywords, String, null: true 13 | field :meta_title, String, null: true 14 | field :name, String, null: false 15 | field :option_types, OptionType.connection_type, null: false 16 | field :product_properties, ProductProperty.connection_type, null: false 17 | field :slug, String, null: false 18 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 19 | field :variants, Variant.connection_type, null: false 20 | 21 | def master_variant 22 | Queries::Product::MasterVariantQuery.new(product: object).call 23 | end 24 | 25 | def option_types 26 | Queries::Product::OptionTypesQuery.new(product: object).call 27 | end 28 | 29 | def product_properties 30 | Queries::Product::ProductPropertiesQuery.new(product: object).call 31 | end 32 | 33 | def variants 34 | Queries::Product::VariantsQuery.new(product: object).call 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/product_property.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class ProductProperty < Base::RelayNode 6 | description 'Product Property.' 7 | 8 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 9 | field :position, Int, null: false 10 | field :property, Property, null: true 11 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 12 | field :value, String, null: true 13 | 14 | def property 15 | Queries::ProductProperty::PropertyQuery.new(product_property: object).call 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/promotion_adjustment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class PromotionAdjustment < Base::RelayNode 6 | implements Types::Interfaces::Adjustment 7 | 8 | description 'PromotionAdjustment.' 9 | 10 | field :promotion_code, String, null: true 11 | 12 | def promotion_code 13 | object.promotion_code&.value 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/property.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class Property < Base::RelayNode 6 | description 'Property.' 7 | 8 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 9 | field :name, String, null: false 10 | field :presentation, String, null: false 11 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/shipment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class Shipment < Base::RelayNode 6 | description 'Order Shipment.' 7 | 8 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 9 | field :number, String, null: false 10 | field :shipped_at, GraphQL::Types::ISO8601DateTime, null: true 11 | field :shipping_rates, ShippingRate.connection_type, null: false 12 | field :state, String, null: false 13 | field :tracking, String, null: true 14 | field :tracking_url, String, null: true 15 | field :manifest, [Types::ManifestItem], null: false 16 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 17 | 18 | def shipping_rates 19 | Queries::Shipment::ShippingRatesQuery.new(shipment: object).call 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/shipping_method.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class ShippingMethod < Base::RelayNode 6 | description 'Shipping Method.' 7 | 8 | field :name, String, null: false 9 | field :tracking_url, String, null: true 10 | field :admin_name, String, null: true 11 | field :code, String, null: true 12 | field :carrier, String, null: true 13 | field :service_level, String, null: true 14 | field :available_to_users, Boolean, null: false 15 | field :available_to_all, Boolean, null: false 16 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 17 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/shipping_rate.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class ShippingRate < Base::RelayNode 6 | description 'Shipping Rate.' 7 | 8 | field :cost, String, null: false 9 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 10 | field :currency, String, null: false 11 | field :selected, Boolean, null: false 12 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 13 | field :shipping_method, ShippingMethod, null: false 14 | 15 | def shipping_method 16 | Queries::ShippingRate::ShippingMethodQuery.new(shipping_rate: object).call 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/state.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class State < Base::RelayNode 6 | description 'State.' 7 | 8 | field :name, String, null: false 9 | field :abbr, String, null: false 10 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 11 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/store.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class Store < Base::RelayNode 6 | description 'Store.' 7 | 8 | field :cart_tax_country_iso, String, null: true 9 | field :code, String, null: false 10 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 11 | field :currencies, Currency.connection_type, null: false 12 | field :default_currency, String, null: true 13 | field :default, Boolean, null: false 14 | field :mail_from_address, String, null: false 15 | field :meta_description, String, null: true 16 | field :meta_keywords, String, null: true 17 | field :name, String, null: false 18 | field :seo_title, String, null: true 19 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 20 | field :url, String, null: false 21 | 22 | def currencies 23 | Spree::Config.available_currencies 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/tax_adjustment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class TaxAdjustment < Base::RelayNode 6 | implements Types::Interfaces::Adjustment 7 | 8 | description 'TaxAdjustment.' 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/taxon.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class Taxon < Base::RelayNode 6 | description 'Taxon.' 7 | 8 | field :children, Taxon.connection_type, null: true 9 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 10 | field :description, String, null: true 11 | field :icon_url, String, null: true 12 | field :meta_description, String, null: true 13 | field :meta_keywords, String, null: true 14 | field :meta_title, String, null: true 15 | field :name, String, null: false 16 | field :parent_taxon, Types::Taxon, null: true 17 | field :permalink, String, null: false 18 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 19 | 20 | def icon_url 21 | object.icon.url 22 | end 23 | 24 | def parent_taxon 25 | Queries::Taxon::ParentTaxonQuery.new(taxon: object).call 26 | end 27 | 28 | def children 29 | Queries::Taxon::ChildrenQuery.new(taxon: object).call 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/taxonomy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class Taxonomy < Base::RelayNode 6 | description 'Taxonomy.' 7 | 8 | field :name, String, null: false 9 | field :root_taxon, Taxon, null: true 10 | field :taxons, Taxon.connection_type, null: false 11 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 12 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 13 | 14 | def root_taxon 15 | Queries::Taxonomy::RootTaxonQuery.new(taxonomy: object).call 16 | end 17 | 18 | def taxons 19 | Queries::Taxonomy::TaxonsQuery.new(taxonomy: object).call 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class User < Base::RelayNode 6 | description 'User.' 7 | 8 | field :addresses, Types::Address.connection_type, null: false 9 | field :bill_address, Types::Address, null: true 10 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 11 | field :current_sign_in_at, GraphQL::Types::ISO8601DateTime, null: true 12 | field :ship_address, Types::Address, null: true 13 | field :email, String, null: false 14 | field :last_sign_in_at, GraphQL::Types::ISO8601DateTime, null: true 15 | field :login, String, null: true 16 | field :ship_address, Types::Address, null: true 17 | field :sign_in_count, Integer, null: false 18 | field :spree_api_key, String, null: true 19 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 20 | field :wallet, Types::WalletPaymentSource.connection_type, method: :wallet_payment_sources, null: false 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/user_error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class UserError < Types::Base::Object 6 | description "A user-readable error" 7 | 8 | field :message, String, null: false, description: "A description of the error" 9 | field :path, [String], null: true, description: "Which input value this error came from" 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/variant.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class Variant < Base::RelayNode 6 | description 'Variant.' 7 | 8 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 9 | field :default_price, Price, null: false 10 | field :depth, String, null: true 11 | field :height, String, null: true 12 | field :images, Types::Image.connection_type, null: false 13 | field :is_master, Boolean, null: false 14 | field :option_values, OptionValue.connection_type, null: false 15 | field :position, Int, null: false 16 | field :prices, Price.connection_type, null: false 17 | field :sku, String, null: false 18 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 19 | field :weight, String, null: false 20 | field :width, String, null: true 21 | 22 | def default_price 23 | Queries::Variant::DefaultPriceQuery.new(variant: object).call 24 | end 25 | 26 | def images 27 | Queries::Variant::ImagesQuery.new(variant: object).call 28 | end 29 | 30 | def option_values 31 | Queries::Variant::OptionValuesQuery.new(variant: object).call 32 | end 33 | 34 | def prices 35 | Queries::Variant::PricesQuery.new(variant: object).call 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/types/wallet_payment_source.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | module Types 5 | class WalletPaymentSource < Base::RelayNode 6 | description 'Wallet Payment Source.' 7 | 8 | field :created_at, GraphQL::Types::ISO8601DateTime, null: true 9 | field :default, Boolean, null: false 10 | field :payment_source, Types::Interfaces::PaymentSource, null: true 11 | field :updated_at, GraphQL::Types::ISO8601DateTime, null: true 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | VERSION = '0.3.1' 5 | end 6 | -------------------------------------------------------------------------------- /solidus_graphql_api.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'lib/solidus_graphql_api/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'solidus_graphql_api' 7 | spec.version = SolidusGraphqlApi::VERSION 8 | spec.authors = ['Christian Rimondi', 'Alessio Rocco', 'Rainer Dema'] 9 | spec.email = 'contact@solidus.io' 10 | 11 | spec.summary = 'Solidus GraphQL API' 12 | spec.description = 'GraphQL comes to Solidus' 13 | spec.homepage = 'https://github.com/solidusio/solidus_graphql_api' 14 | spec.license = 'BSD-3-Clause' 15 | 16 | spec.metadata['homepage_uri'] = spec.homepage 17 | spec.metadata['source_code_uri'] = 'https://github.com/solidusio/solidus_graphql_api' 18 | spec.metadata['changelog_uri'] = 'https://github.com/solidusio/solidus_graphql_api/releases' 19 | 20 | spec.required_ruby_version = Gem::Requirement.new('~> 3.0') 21 | 22 | # Specify which files should be added to the gem when it is released. 23 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 24 | files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") } 25 | 26 | spec.files = files.grep_v(%r{^(test|spec|features)/}) 27 | spec.test_files = files.grep(%r{^(test|spec|features)/}) 28 | spec.bindir = "exe" 29 | spec.executables = files.grep(%r{^exe/}) { |f| File.basename(f) } 30 | spec.require_paths = ["lib"] 31 | 32 | spec.add_dependency 'batch-loader', '~> 2.0' 33 | spec.add_dependency 'graphql', '>= 1.10', '< 1.13' 34 | spec.add_dependency 'solidus_core', ['>= 3.2', '< 5'] 35 | spec.add_dependency 'solidus_support', '~> 0.6' 36 | 37 | spec.add_development_dependency 'graphql-docs', '~> 2.0.1' 38 | spec.add_development_dependency 'graphql-schema_comparator', '~> 1.0.0' 39 | spec.add_development_dependency 'pry', '~> 0.14' 40 | spec.add_development_dependency 'simplecov', '~> 0.21' 41 | spec.add_development_dependency 'solidus_dev_support', '~> 2.4' 42 | spec.add_development_dependency 'timecop', '~> 0.9.1' 43 | end 44 | -------------------------------------------------------------------------------- /spec/integration/mutations/add_to_cart_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe_mutation :add_to_cart, mutation: :add_to_cart do 6 | let(:current_ability) { Spree::Ability.new(nil) } 7 | let(:guest_token) { nil } 8 | let(:mutation_context) { 9 | Hash[ 10 | current_ability: current_ability, 11 | current_order: current_order, 12 | order_token: guest_token 13 | ] 14 | } 15 | 16 | let(:variant) { create(:variant) } 17 | let(:variant_id) { 18 | SolidusGraphqlApi::Schema.id_from_object(variant, nil, nil) 19 | } 20 | let(:quantity) { 10 } 21 | let(:mutation_variables) { 22 | Hash[ 23 | input: { 24 | variantId: variant_id, 25 | quantity: quantity 26 | } 27 | ] 28 | } 29 | 30 | shared_examples "responds with an unauthorized error" do 31 | it { expect(subject[:data][:addToCart]).to be_nil } 32 | it { expect(subject[:errors].first[:message]).to eq I18n.t(:"unauthorized.default") } 33 | end 34 | 35 | context "when current order isn't present" do 36 | let(:current_order) { nil } 37 | 38 | include_examples "responds with an unauthorized error" 39 | end 40 | 41 | context "when current order is completed" do 42 | let(:current_order) { create(:completed_order_with_totals) } 43 | 44 | include_examples "responds with an unauthorized error" 45 | end 46 | 47 | context "when current order isn't completed" do 48 | let(:current_order) { create(:order, state: 'payment') } 49 | let(:user_errors) { subject[:data][:addToCart][:errors] } 50 | 51 | context 'and the current ability is unauthorized' do 52 | include_examples "responds with an unauthorized error" 53 | end 54 | 55 | context 'and the current ability is authorized' do 56 | let(:guest_token) { current_order.guest_token } 57 | let(:response_order) { subject[:data][:addToCart][:order] } 58 | 59 | it { expect(response_order[:number]).to eq(current_order.number) } 60 | it { expect(response_order[:state]).to eq('address') } 61 | it { expect(response_order[:lineItems][:nodes].first[:quantity]).to eq(10) } 62 | it { expect(response_order[:lineItems][:nodes].first[:variant][:id]).to eq(variant.id) } 63 | it { expect(user_errors).to be_empty } 64 | it { is_expected.to_not have_key(:errors) } 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/integration/mutations/checkout/complete_checkout_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe_mutation :complete_checkout, mutation: :complete_checkout do 6 | let(:current_ability) { Spree::Ability.new(nil) } 7 | let(:guest_token) { nil } 8 | let(:mutation_context) { 9 | Hash[ 10 | current_ability: current_ability, 11 | current_order: current_order, 12 | order_token: guest_token 13 | ] 14 | } 15 | 16 | let(:mutation_variables) { Hash[input: {}] } 17 | 18 | shared_examples "responds with an unauthorized error" do 19 | it { expect(subject[:data][:completeCheckout]).to be_nil } 20 | it { expect(subject[:errors].first[:message]).to eq I18n.t(:"unauthorized.default") } 21 | end 22 | 23 | context "when current order isn't present" do 24 | let(:current_order) { nil } 25 | 26 | include_examples "responds with an unauthorized error" 27 | end 28 | 29 | context "when current order isn't ready to be complete" do 30 | let(:current_order) { create(:order_with_line_items) } 31 | 32 | include_examples "responds with an unauthorized error" 33 | end 34 | 35 | context "when current order is ready to be complete" do 36 | let(:current_order) { create(:order_ready_to_complete) } 37 | let(:user_errors) { subject[:data][:completeCheckout][:errors] } 38 | 39 | context 'and the current ability is unauthorized' do 40 | include_examples "responds with an unauthorized error" 41 | end 42 | 43 | context 'and the current ability is authorized' do 44 | let(:guest_token) { current_order.guest_token } 45 | let(:response_order) { subject[:data][:completeCheckout][:order] } 46 | 47 | context 'and the given current order state can advance' do 48 | it { expect(response_order[:number]).to eq(current_order.number) } 49 | it { expect(response_order[:email]).to eq(current_order.email) } 50 | it { expect(response_order[:state]).to eq('complete') } 51 | it { expect(user_errors).to be_empty } 52 | it { is_expected.to_not have_key(:errors) } 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/integration/mutations/create_order_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe_mutation :create_order, mutation: :create_order do 6 | let(:current_user) { create(:user) } 7 | let(:current_ability) { Spree::Ability.new(nil) } 8 | let(:current_store) { create(:store) } 9 | let(:mutation_context) { 10 | Hash[ 11 | current_user: current_user, 12 | current_ability: current_ability, 13 | current_store: current_store 14 | ] 15 | } 16 | 17 | let(:mutation_variables) { 18 | Hash[ 19 | input: {} 20 | ] 21 | } 22 | 23 | context 'when the current ability is unauthorized' do 24 | before do 25 | current_ability.cannot :create, Spree::Order 26 | end 27 | 28 | it { expect(subject[:data][:createOrder]).to be_nil } 29 | it { expect(subject[:errors].first[:message]).to eq I18n.t(:"unauthorized.default") } 30 | end 31 | 32 | context 'when the current ability is authorized' do 33 | let(:user_errors) { subject[:data][:createOrder][:errors] } 34 | let(:response_order) { subject[:data][:createOrder][:order] } 35 | 36 | context 'and the user is authenticated' do 37 | let(:last_incomplete_order) { current_user.last_incomplete_spree_order(store: current_store) } 38 | 39 | it { expect(response_order[:number]).to eq(last_incomplete_order.number) } 40 | it { expect(response_order[:state]).to eq('cart') } 41 | it { expect(user_errors).to be_empty } 42 | it { is_expected.to_not have_key(:errors) } 43 | end 44 | 45 | context 'and the user is guest' do 46 | let(:current_user) { nil } 47 | let(:last_incomplete_order) { Spree::Order.incomplete.where(store: current_store).last } 48 | 49 | it { expect(response_order[:number]).to eq(last_incomplete_order.number) } 50 | it { expect(response_order[:state]).to eq('cart') } 51 | it { expect(user_errors).to be_empty } 52 | it { is_expected.to_not have_key(:errors) } 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/integration/mutations/empty_cart_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe_mutation :empty_cart, mutation: :empty_cart do 6 | let(:current_ability) { Spree::Ability.new(nil) } 7 | let(:guest_token) { nil } 8 | let(:mutation_context) { 9 | Hash[ 10 | current_ability: current_ability, 11 | current_order: current_order, 12 | order_token: guest_token 13 | ] 14 | } 15 | 16 | let(:mutation_variables) { 17 | Hash[ 18 | input: {} 19 | ] 20 | } 21 | 22 | shared_examples "responds with an unauthorized error" do 23 | it { expect(subject[:data][:emptyCart]).to be_nil } 24 | it { expect(subject[:errors].first[:message]).to eq I18n.t(:"unauthorized.default") } 25 | end 26 | 27 | context "when current order isn't present" do 28 | let(:current_order) { nil } 29 | 30 | include_examples "responds with an unauthorized error" 31 | end 32 | 33 | context "when current order is completed" do 34 | let(:current_order) { create(:completed_order_with_totals) } 35 | 36 | include_examples "responds with an unauthorized error" 37 | end 38 | 39 | context "when current order isn't completed" do 40 | let(:current_order) { create(:order_with_line_items, state: 'address') } 41 | let(:user_errors) { subject[:data][:emptyCart][:errors] } 42 | 43 | context 'and the current ability is unauthorized' do 44 | include_examples "responds with an unauthorized error" 45 | end 46 | 47 | context 'and the current ability is authorized' do 48 | let(:guest_token) { current_order.guest_token } 49 | let(:response_order) { subject[:data][:emptyCart][:order] } 50 | 51 | it { expect(response_order[:number]).to eq(current_order.number) } 52 | it { expect(response_order[:state]).to eq('cart') } 53 | it { expect(response_order[:lineItems][:nodes]).to be_empty } 54 | it { expect(user_errors).to be_empty } 55 | it { is_expected.to_not have_key(:errors) } 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/integration/mutations/mark_default_ship_address_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe_mutation :mark_default_ship_address, mutation: :mark_default_ship_address do 6 | let(:mutation_context) { Hash[current_ability: Spree::Ability.new(current_user), current_user: current_user] } 7 | let(:mutation_variables) { Hash[input: { addressId: SolidusGraphqlApi::Schema.id_from_object(address, nil, nil) }] } 8 | 9 | let(:current_user) { create(:user_with_addresses) } 10 | let(:address) { create(:address) } 11 | 12 | context "when current user isn't present" do 13 | let(:current_user) { nil } 14 | 15 | it { expect(subject[:data][:markDefaultShipAddress]).to be_nil } 16 | it { expect(subject[:errors].first[:message]).to eq I18n.t(:"unauthorized.default") } 17 | end 18 | 19 | context "when current user is present" do 20 | context "and the given address id is wrong" do 21 | it { expect(subject[:data][:markDefaultShipAddress]).to be_nil } 22 | it { expect(subject[:errors].first[:message]).to eq I18n.t(:'activerecord.exceptions.not_found') } 23 | end 24 | 25 | context "and the given address id is correct" do 26 | before { current_user.addresses << address } 27 | 28 | let(:ship_address) { subject[:data][:markDefaultShipAddress][:user][:shipAddress] } 29 | 30 | it { expect(ship_address[:id]).to eq address.id } 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/integration/mutations/remove_from_address_book_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe_mutation :remove_from_address_book, mutation: :remove_from_address_book do 6 | let(:mutation_context) { Hash[current_ability: Spree::Ability.new(current_user), current_user: current_user] } 7 | let(:mutation_variables) { Hash[input: { addressId: SolidusGraphqlApi::Schema.id_from_object(address, nil, nil) }] } 8 | 9 | let(:address) { create(:address) } 10 | let(:current_user) { create(:user_with_addresses) } 11 | 12 | context "when current user isn't present" do 13 | let(:current_user) { nil } 14 | 15 | it { expect(subject[:data][:removeFromAddressBook]).to be_nil } 16 | it { expect(subject[:errors].first[:message]).to eq I18n.t(:"unauthorized.default") } 17 | end 18 | 19 | context "when current user is present" do 20 | context "and the given address id is wrong" do 21 | it { expect(subject[:data][:removeFromAddressBook]).to be_nil } 22 | it { expect(subject[:errors].first[:message]).to eq I18n.t(:"unauthorized.default") } 23 | end 24 | 25 | context "and the given address id is correct" do 26 | before { current_user.addresses << address } 27 | 28 | let(:user_addresses) { subject[:data][:removeFromAddressBook][:user][:addresses][:nodes] } 29 | 30 | it { expect(user_addresses.count).to eq 2 } 31 | it { expect(user_addresses.map{ |address| address[:id] }).to_not include(address.id) } 32 | 33 | describe 'default address' do 34 | let(:ship_address) { subject[:data][:removeFromAddressBook][:user][:shipAddress] } 35 | 36 | context "when address is the default" do 37 | before { current_user.mark_default_ship_address(address.id) } 38 | 39 | it { expect(ship_address).to be_nil } 40 | end 41 | 42 | context "when address isn't the default" do 43 | it { expect(ship_address[:id]).to_not eq address.id } 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/integration/mutations/remove_from_cart_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe_mutation :remove_from_cart, mutation: :remove_from_cart do 6 | let(:current_ability) { Spree::Ability.new(nil) } 7 | let(:guest_token) { nil } 8 | let(:mutation_context) { 9 | Hash[ 10 | current_ability: current_ability, 11 | current_order: current_order, 12 | order_token: guest_token 13 | ] 14 | } 15 | 16 | let(:line_item) { current_order.line_items.first } 17 | let(:line_item_id) { 18 | SolidusGraphqlApi::Schema.id_from_object(line_item, nil, nil) 19 | } 20 | let(:mutation_variables) { 21 | Hash[ 22 | input: { 23 | lineItemId: line_item_id 24 | } 25 | ] 26 | } 27 | 28 | shared_examples "responds with an unauthorized error" do 29 | it { expect(subject[:data][:removeFromCart]).to be_nil } 30 | it { expect(subject[:errors].first[:message]).to eq I18n.t(:"unauthorized.default") } 31 | end 32 | 33 | context "when current order isn't present" do 34 | let(:current_order) { nil } 35 | let(:line_item_id) { 'not_existent' } 36 | 37 | include_examples "responds with an unauthorized error" 38 | end 39 | 40 | context "when current order is completed" do 41 | let(:current_order) { create(:completed_order_with_totals) } 42 | 43 | include_examples "responds with an unauthorized error" 44 | end 45 | 46 | context "when current order isn't completed" do 47 | let(:current_order) { create(:order_with_line_items, state: 'address') } 48 | let(:user_errors) { subject[:data][:removeFromCart][:errors] } 49 | 50 | context 'and the current ability is unauthorized' do 51 | include_examples "responds with an unauthorized error" 52 | end 53 | 54 | context 'and the current ability is authorized' do 55 | let(:guest_token) { current_order.guest_token } 56 | let(:response_order) { subject[:data][:removeFromCart][:order] } 57 | 58 | it { expect(response_order[:number]).to eq(current_order.number) } 59 | it { expect(response_order[:state]).to eq('cart') } 60 | it { expect(response_order[:lineItems][:nodes]).to be_empty } 61 | it { expect(user_errors).to be_empty } 62 | it { is_expected.to_not have_key(:errors) } 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/integration/mutations/set_order_email_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe_mutation :set_order_email, mutation: :set_order_email do 6 | let(:current_ability) { Spree::Ability.new(nil) } 7 | let(:guest_token) { nil } 8 | let(:mutation_context) { 9 | Hash[ 10 | current_ability: current_ability, 11 | current_order: current_order, 12 | order_token: guest_token 13 | ] 14 | } 15 | 16 | let(:email) { 'test@example.com' } 17 | let(:mutation_variables) { 18 | Hash[ 19 | input: { 20 | email: email 21 | } 22 | ] 23 | } 24 | 25 | shared_examples "responds with an unauthorized error" do 26 | it { expect(subject[:data][:setOrderEmail]).to be_nil } 27 | it { expect(subject[:errors].first[:message]).to eq I18n.t(:"unauthorized.default") } 28 | end 29 | 30 | context "when current order isn't present" do 31 | let(:current_order) { nil } 32 | 33 | include_examples "responds with an unauthorized error" 34 | end 35 | 36 | context "when current order is completed" do 37 | let(:current_order) { create(:completed_order_with_totals) } 38 | 39 | include_examples "responds with an unauthorized error" 40 | end 41 | 42 | context "when current order isn't completed" do 43 | let(:current_order) { create(:order, state: 'cart') } 44 | let(:user_errors) { subject[:data][:setOrderEmail][:errors] } 45 | 46 | context 'and the current ability is unauthorized' do 47 | include_examples "responds with an unauthorized error" 48 | end 49 | 50 | context 'and the current ability is authorized' do 51 | let(:guest_token) { current_order.guest_token } 52 | let(:response_order) { subject[:data][:setOrderEmail][:order] } 53 | 54 | it { expect(response_order[:number]).to eq(current_order.number) } 55 | it { expect(response_order[:state]).to eq('cart') } 56 | it { expect(response_order[:email]).to eq(email) } 57 | it { expect(user_errors).to be_empty } 58 | it { is_expected.to_not have_key(:errors) } 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/integration/mutations/update_cart_quantity_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe_mutation :update_cart_quantity, mutation: :update_cart_quantity do 6 | let(:current_ability) { Spree::Ability.new(nil) } 7 | let(:guest_token) { nil } 8 | let(:mutation_context) { 9 | Hash[ 10 | current_ability: current_ability, 11 | current_order: current_order, 12 | order_token: guest_token 13 | ] 14 | } 15 | 16 | let(:line_item) { current_order.line_items.first } 17 | let(:line_item_id) { 18 | SolidusGraphqlApi::Schema.id_from_object(line_item, nil, nil) 19 | } 20 | let(:quantity) { 10 } 21 | let(:mutation_variables) { 22 | Hash[ 23 | input: { 24 | lineItemId: line_item_id, 25 | quantity: quantity 26 | } 27 | ] 28 | } 29 | 30 | shared_examples "responds with an unauthorized error" do 31 | it { expect(subject[:data][:updateCartQuantity]).to be_nil } 32 | it { expect(subject[:errors].first[:message]).to eq I18n.t(:"unauthorized.default") } 33 | end 34 | 35 | context "when current order isn't present" do 36 | let(:current_order) { nil } 37 | let(:line_item_id) { 'not_existent' } 38 | 39 | include_examples "responds with an unauthorized error" 40 | end 41 | 42 | context "when current order is completed" do 43 | let(:current_order) { create(:completed_order_with_totals) } 44 | 45 | include_examples "responds with an unauthorized error" 46 | end 47 | 48 | context "when current order isn't completed" do 49 | let(:current_order) { create(:order_with_line_items, state: 'delivery') } 50 | let(:user_errors) { subject[:data][:updateCartQuantity][:errors] } 51 | 52 | context 'and the current ability is unauthorized' do 53 | include_examples "responds with an unauthorized error" 54 | end 55 | 56 | context 'and the current ability is authorized' do 57 | let(:guest_token) { current_order.guest_token } 58 | let(:response_order) { subject[:data][:updateCartQuantity][:order] } 59 | 60 | it { expect(response_order[:number]).to eq(current_order.number) } 61 | it { expect(response_order[:state]).to eq('address') } 62 | it { expect(response_order[:lineItems][:nodes].first[:id]).to eq(line_item.id) } 63 | it { expect(response_order[:lineItems][:nodes].first[:quantity]).to eq(10) } 64 | it { expect(user_errors).to be_empty } 65 | it { is_expected.to_not have_key(:errors) } 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/integration/queries/countries_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe_query :countries, query: :countries, freeze_date: true do 6 | connection_field :countries do 7 | context 'when countries does not exists' do 8 | it { expect(subject.dig(:data, :countries, :nodes)).to eq [] } 9 | end 10 | 11 | context 'when countries exists' do 12 | let!(:us_country) { create(:country, :with_states, id: 1, states_required: false) } 13 | let!(:it_country) { create(:country, id: 2, iso: 'IT', states_required: false) } 14 | 15 | it { is_expected.to match_response(:countries) } 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/integration/queries/current_order_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe_query :currentOrder, query: :current_order, freeze_date: true do 6 | let!(:order) { create :order_with_line_items, id: 1, number: 'fake number' } 7 | 8 | let(:query_context) { { current_order: order } } 9 | let(:shipment_number) { order.shipments.first.number } 10 | let(:variant_sku) { order.line_items.first.variant.sku } 11 | 12 | field :currentOrder do 13 | it { 14 | expect(subject).to match_response(:current_order).with_args(shipment_number: shipment_number, 15 | variant_sku: variant_sku) 16 | } 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/integration/queries/current_store_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe_query :currentStore, query: :current_store, freeze_date: true do 6 | let!(:store) { create(:store, :with_defaults, mail_from_address: 'solidus_graphql_api@example.org') } 7 | 8 | before { Spree::Config.available_currencies = ::Money::Currency.first(2) } 9 | 10 | let(:query_context) { Hash[current_store: store] } 11 | 12 | field :currentStore do 13 | it { is_expected.to match_response(:current_store) } 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/integration/queries/current_user_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe_query :current_user, query: :current_user, freeze_date: true do 6 | let(:user) { 7 | create(:user, 8 | id: 1, 9 | email: 'email@example.com', 10 | spree_api_key: '123', 11 | ship_address: ship_address, 12 | bill_address: bill_address) 13 | } 14 | 15 | # Previos to Solidus v3.3, states_required was always false and the state was 16 | # always automatically assigned. We force the behavior here to have the same 17 | # response for any Solidus version. 18 | let(:country) { create(:country, states_required: false) } 19 | let(:state) { create(:state, name: "Alabama", abbr: "AL", country: country) } 20 | let(:ship_address) { create(:ship_address, id: 1, zipcode: 10_001, country: country, state: state) } 21 | let(:bill_address) { create(:bill_address, id: 2, zipcode: 10_002, country: country, state: state) } 22 | let(:credit_card) { 23 | create(:credit_card, 24 | user: user, 25 | cc_type: 'visa', 26 | address: ship_address, 27 | payment_method: create(:payment_method, id: 1)) 28 | } 29 | 30 | before do 31 | wallet_payment_source = user.wallet.add credit_card 32 | wallet_payment_source.update!(default: true) 33 | end 34 | 35 | field :currentUser do 36 | context 'when context does not have current user' do 37 | it { expect(subject.dig(:data, :currentUser)).to be_nil } 38 | end 39 | 40 | context 'when context have current user' do 41 | let!(:query_context) { Hash[current_user: user] } 42 | 43 | let(:args) { { wallet: user.wallet } } 44 | 45 | it { is_expected.to match_response(:current_user).with_args(args) } 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/integration/queries/product_by_slug_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe_query :product_by_slug, query: :product_by_slug, freeze_date: true do 6 | let!(:product) { create(:product, id: 1, name: 'Product', slug: 'slug', sku: 'SKU') } 7 | 8 | let!(:option_type) { create(:option_type, id: 1, name: 'foo-size') } 9 | let!(:option_value) { create(:option_value, id: 1, name: 'Size', option_type: option_type) } 10 | let!(:product_option_type) { create(:product_option_type, id: 1, product: product, option_type: option_type) } 11 | 12 | let!(:product_property) { create(:product_property, id: 1, product: product) } 13 | 14 | let!(:first_variant) { create(:base_variant, product: product) } 15 | let!(:second_variant) { create(:base_variant, product: product) } 16 | 17 | before do 18 | product.master.update!(option_values: [option_value]) 19 | end 20 | 21 | field :productBySlug do 22 | context "when the slug doesn't exist" do 23 | let(:query_variables) { { slug: 'non-existent-slug' } } 24 | 25 | it { expect(subject.dig(:data, :productBySlug)).to be_nil } 26 | end 27 | 28 | context 'when the slug exists' do 29 | let(:query_variables) { { slug: 'slug' } } 30 | 31 | let(:args) do 32 | { 33 | product: product, 34 | first_variant: first_variant, 35 | second_variant: second_variant 36 | } 37 | end 38 | 39 | it { is_expected.to match_response(:product_by_slug).with_args(args) } 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/integration/queries/products_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe_query :products, query: :products, freeze_date: true do 6 | connection_field :products do 7 | subject { query_response.dig(:data, :products, :nodes) } 8 | 9 | let(:current_user) { create :user } 10 | let(:current_pricing_options) { Spree::Config.pricing_options_class.new } 11 | let(:query_context) do 12 | { current_user: current_user, 13 | current_pricing_options: current_pricing_options } 14 | end 15 | 16 | context 'when products do not exist' do 17 | it { is_expected.to eq [] } 18 | end 19 | 20 | context 'when products exist' do 21 | let!(:solidus_t_shirt) { create(:product, name: 'Solidus T-Shirt', id: 1, price: 19.99) } 22 | let!(:ruby_mug) { create(:product, name: 'Ruby Mug', id: 2, price: 9.99) } 23 | let!(:solidus_mug) { create(:product, name: 'Solidus Mug ', id: 3, price: 9.99) } 24 | let!(:solidus_tote) { create(:product, name: 'Solidus Tote', id: 4, price: 15.99) } 25 | 26 | context 'when no query is passed' do 27 | it { 28 | expect(subject).to match_array([{ id: solidus_t_shirt.id }, { id: ruby_mug.id }, { id: solidus_mug.id }, 29 | { id: solidus_tote.id }]) 30 | } 31 | end 32 | 33 | context 'when a query is passed' do 34 | let(:taxon) { create(:taxon, products: [solidus_tote]) } 35 | let(:search) { { price_range_any: ["Under $10.00", "$15.00 - $18.00"] } } 36 | let(:taxon_id) { SolidusGraphqlApi::Schema.id_from_object(taxon, nil, nil) } 37 | let(:query_variables) { { query: { taxon: taxon_id, keywords: "Solidus", search: search } } } 38 | 39 | it { is_expected.to match_array([{ id: solidus_tote.id }]) } 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/integration/queries/taxonomies_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe_query :taxonomies do 6 | connection_field :taxonomies, query: :taxonomies, freeze_date: true do 7 | context 'when taxonomies does not exists' do 8 | it { expect(subject.dig(:data, :taxonomies, :nodes)).to eq [] } 9 | end 10 | 11 | context 'when taxonomies exists' do 12 | let!(:brand_taxonomy) do 13 | create(:taxonomy, :with_taxon_meta, :with_root_icon, id: 1, name: "Brand", root_taxon_id: 1) 14 | end 15 | let!(:category_taxonomy) { create(:taxonomy, :with_root_icon, id: 2, name: 'Category', root_taxon_id: 2) } 16 | # Need to reload taxon to get the correct icon url, because the icon is attached after the taxon is created 17 | let!(:taxon) { create(:taxon, :with_icon, id: 3, name: 'Solidus', taxonomy: brand_taxonomy).tap(&:reload) } 18 | 19 | before do 20 | taxon.update(parent: brand_taxonomy.root) 21 | end 22 | 23 | it { 24 | expect(subject).to match_response(:taxonomies).with_args(brand_url: brand_taxonomy.taxons.first.icon.url, 25 | category_url: category_taxonomy.taxons.first.icon.url, 26 | taxon_url: taxon.icon.url) 27 | } 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/integration/types/json_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe 'JSON type' do 6 | it 'coerces JSON custom type', type: :feature do 7 | create(:product, name: 'Shirt') 8 | 9 | post '/graphql', 10 | params: { 11 | query: "{ products(query: { search: { name_cont: \"Shirt\" } }) { nodes { name } } }" 12 | }, 13 | headers: headers 14 | 15 | expect(JSON.parse(response.body).dig(*%w[data products nodes])[0]['name']).to eq('Shirt') 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/batch_loader/belongs_to_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::BatchLoader::BelongsTo, skip: (ENV["DB"] == "mysql") do 6 | include Helpers::ActiveRecord 7 | 8 | subject(:loader) do 9 | described_class.new( 10 | object, 11 | object.class.reflect_on_association(association), 12 | options 13 | ) 14 | end 15 | 16 | let(:options) { {} } 17 | 18 | context 'with a regular association' do 19 | before do 20 | run_migrations do 21 | create_table :articles, force: true 22 | create_table :comments, force: true do |t| 23 | t.belongs_to :article 24 | end 25 | end 26 | create_model("Article") 27 | create_model("Comment") { belongs_to :article } 28 | end 29 | 30 | after do 31 | run_migrations do 32 | drop_table :articles 33 | drop_table :comments 34 | end 35 | end 36 | 37 | let!(:object) { Comment.create!(article: Article.create!) } 38 | let(:association) { :article } 39 | 40 | it 'loads the association properly' do 41 | expect(loader.load.sync).to eq(object.article) 42 | end 43 | end 44 | 45 | context 'with a polymorphic association' do 46 | before do 47 | run_migrations do 48 | create_table :images, force: true do |t| 49 | t.integer :imageable_id 50 | t.string :imageable_type 51 | end 52 | create_table :articles, force: true 53 | end 54 | create_model("Image") { belongs_to :imageable, polymorphic: true } 55 | create_model("Article") { has_many :images, as: :imageable } 56 | end 57 | 58 | let!(:object) { Image.create!(imageable: Article.create!) } 59 | let(:association) { :imageable } 60 | let(:options) { { klass: Article } } 61 | 62 | it 'loads the association properly' do 63 | expect(loader.load.sync).to eq(object.imageable) 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/batch_loader/has_many_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::BatchLoader::HasMany, skip: (ENV["DB"] == "mysql") do 6 | include Helpers::ActiveRecord 7 | 8 | subject(:loader) do 9 | described_class.new( 10 | article, 11 | Article.reflect_on_association(:comments) 12 | ) 13 | end 14 | 15 | before do 16 | run_migrations do 17 | create_table :articles, force: true 18 | create_table :comments, force: true do |t| 19 | t.belongs_to :article 20 | end 21 | end 22 | create_model("Article") { has_many :comments } 23 | create_model("Comment") { belongs_to :article } 24 | 25 | article.comments.create! 26 | end 27 | 28 | after do 29 | run_migrations do 30 | drop_table :articles 31 | drop_table :comments 32 | end 33 | end 34 | 35 | let!(:article) { Article.create! } 36 | 37 | it 'loads the association properly' do 38 | expect(loader.load.sync).to eq(article.comments) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/batch_loader/has_many_through_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::BatchLoader::HasManyThrough, skip: (ENV["DB"] == "mysql") do 6 | include Helpers::ActiveRecord 7 | 8 | subject(:loader) do 9 | described_class.new( 10 | physician, 11 | Physician.reflect_on_association(:patients) 12 | ) 13 | end 14 | 15 | before do 16 | run_migrations do 17 | create_table :physicians, force: true 18 | create_table :patients, force: true 19 | create_table :appointments, force: true do |t| 20 | t.belongs_to :physician 21 | t.belongs_to :patient 22 | end 23 | end 24 | create_model("Physician") do 25 | has_many :appointments 26 | has_many :patients, through: :appointments 27 | end 28 | create_model("Patient") do 29 | has_many :appointments 30 | end 31 | create_model("Appointment") do 32 | belongs_to :physician 33 | belongs_to :patient 34 | end 35 | 36 | physician.appointments.create!(patient: Patient.create!) 37 | end 38 | 39 | after do 40 | run_migrations do 41 | drop_table :physicians 42 | drop_table :patients 43 | drop_table :appointments 44 | end 45 | end 46 | 47 | let!(:physician) { Physician.create! } 48 | 49 | it 'loads the association properly' do 50 | expect(loader.load.sync).to eq(physician.patients) 51 | end 52 | 53 | it "doesn't duplicate result when another record loaded in the batch is also associated to the same target record" do 54 | second_physician = Physician.create! 55 | Appointment.create!(patient: physician.patients.first, physician: second_physician) 56 | 57 | described_class.new( 58 | second_physician, 59 | Physician.reflect_on_association(:patients) 60 | ).load 61 | 62 | expect(loader.load.sync.length).to be(1) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/batch_loader/has_one_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::BatchLoader::HasOne, skip: (ENV["DB"] == "mysql") do 6 | include Helpers::ActiveRecord 7 | 8 | subject(:loader) do 9 | described_class.new( 10 | article, 11 | Article.reflect_on_association(:image) 12 | ) 13 | end 14 | 15 | before do 16 | run_migrations do 17 | create_table :articles, force: true 18 | create_table :images, force: true do |t| 19 | t.belongs_to :article 20 | end 21 | end 22 | create_model("Article") { has_one :image } 23 | create_model("Image") { belongs_to :article } 24 | 25 | article.create_image! 26 | end 27 | 28 | after do 29 | run_migrations do 30 | drop_table :articles 31 | drop_table :images 32 | end 33 | end 34 | 35 | let!(:article) { Article.create! } 36 | 37 | it 'loads the association properly' do 38 | expect(loader.load.sync).to eq(article.image) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/address/country_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Address::CountryQuery do 6 | subject { described_class.new(address: address).call.sync } 7 | 8 | let(:address) { create(:address) } 9 | 10 | let(:country) { address.country } 11 | 12 | it { is_expected.to eq(country) } 13 | end 14 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/address/state_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Address::StateQuery do 6 | subject { described_class.new(address: address).call.sync } 7 | 8 | let(:address) { create(:address) } 9 | 10 | let(:state) { address.state } 11 | 12 | it { is_expected.to eq(state) } 13 | end 14 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/completed_orders_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::CompletedOrdersQuery do 6 | subject { described_class.new(user: user).call } 7 | 8 | let(:user) { create :user } 9 | let!(:orders) { create_list(:completed_order_with_totals, 2, user: user) } 10 | 11 | it { is_expected.to eq orders } 12 | end 13 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/countries_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::CountriesQuery do 6 | subject { described_class.new.call } 7 | 8 | let!(:countries) { create_list(:country, 2) } 9 | 10 | it { is_expected.to match_array(countries) } 11 | end 12 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/country/states_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Country::StatesQuery do 6 | let(:country) { create(:country) } 7 | 8 | let!(:states) { create_list(:state, 2, country: country) } 9 | 10 | it { expect(described_class.new(country: country).call.sync).to match_array(states) } 11 | end 12 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/line_item/variant_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::LineItem::VariantQuery do 6 | let(:line_item) { create(:line_item) } 7 | 8 | it { expect(described_class.new(line_item: line_item).call.sync).to eq(line_item.variant) } 9 | end 10 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/option_type/option_values_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::OptionType::OptionValuesQuery do 6 | let(:option_type) { create(:option_type) } 7 | 8 | let!(:option_values) { create_list(:option_value, 2, option_type: option_type) } 9 | 10 | it { expect(described_class.new(option_type: option_type).call.sync).to match_array(option_values) } 11 | end 12 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/option_value/option_type_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::OptionValue::OptionTypeQuery do 6 | let(:option_type) { create(:option_type) } 7 | let(:option_value) { create(:option_value, option_type: option_type) } 8 | 9 | it { expect(described_class.new(option_value: option_value).call.sync).to eq(option_type) } 10 | end 11 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/order/adjustments_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Order::AdjustmentsQuery do 6 | let(:promotion) { create :promotion_with_order_adjustment } 7 | let(:order) { create :completed_order_with_promotion, promotion: promotion } 8 | 9 | it { expect(described_class.new(order: order).call.sync).to eq order.adjustments } 10 | end 11 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/order/billing_address_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Order::BillingAddressQuery do 6 | let(:order) { create :order, bill_address: billing_address } 7 | 8 | let(:billing_address) { create :bill_address } 9 | 10 | it { expect(described_class.new(order: order).call.sync).to eq billing_address } 11 | end 12 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/order/line_items_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Order::LineItemsQuery do 6 | let(:order) { create :order_with_line_items, line_items_count: 2 } 7 | 8 | it { expect(described_class.new(order: order).call.sync).to eq order.line_items } 9 | end 10 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/order/payments_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Order::PaymentsQuery do 6 | let(:order) { create :completed_order_with_pending_payment } 7 | 8 | it { expect(described_class.new(order: order).call.sync).to eq order.payments } 9 | end 10 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/order/shipments_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Order::ShipmentsQuery do 6 | let(:order) { create :order } 7 | let(:shipment) { create(:shipment) } 8 | 9 | context 'when order has shipments' do 10 | before { order.shipments << shipment } 11 | 12 | it { expect(described_class.new(order: order).call.sync).to eq [shipment] } 13 | end 14 | 15 | context 'when order does not have shipments' do 16 | it { expect(described_class.new(order: order).call.sync).to be_empty } 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/order/shipping_address_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Order::ShippingAddressQuery do 6 | let(:order) { create :order, ship_address: shipping_address } 7 | 8 | let(:shipping_address) { create :ship_address } 9 | 10 | it { expect(described_class.new(order: order).call.sync).to eq shipping_address } 11 | end 12 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/product/master_variant_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Product::MasterVariantQuery do 6 | let(:product) { create(:product) } 7 | 8 | let!(:variants) { create_list(:variant, 2, product: product) } 9 | 10 | it { expect(described_class.new(product: product).call.sync).to eq(product.master) } 11 | end 12 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/product/option_types_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Product::OptionTypesQuery do 6 | let(:product) { create(:product) } 7 | 8 | let!(:option_types) { create_list(:option_type, 2, products: [product]) } 9 | 10 | it { expect(described_class.new(product: product).call.sync).to match_array(option_types) } 11 | end 12 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/product/product_properties_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Product::ProductPropertiesQuery do 6 | let(:product) { create(:product) } 7 | 8 | let!(:product_properties) { create_list(:product_property, 2, product: product) } 9 | 10 | it { expect(described_class.new(product: product).call.sync).to match_array(product_properties) } 11 | end 12 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/product/variants_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Product::VariantsQuery do 6 | let(:product) { create(:product) } 7 | 8 | let!(:variants) { create_list(:variant, 2, product: product) } 9 | 10 | it { expect(described_class.new(product: product).call.sync).to match_array(variants) } 11 | end 12 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/product_by_slug_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::ProductBySlugQuery do 6 | subject { described_class.new.call(slug: slug) } 7 | 8 | let!(:products) { create_list(:product, 2) } 9 | let(:product) { products.last } 10 | let(:slug) { product.slug } 11 | 12 | it { is_expected.to eq(product) } 13 | end 14 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/product_property/property_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::ProductProperty::PropertyQuery do 6 | let(:property) { create(:property) } 7 | 8 | let!(:product_property) { create(:product_property, property: property) } 9 | 10 | it { expect(described_class.new(product_property: product_property).call.sync).to eq(property) } 11 | end 12 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/products_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::ProductsQuery do 6 | subject do 7 | described_class.new(user: user, pricing_options: pricing_options).call(query: query) 8 | end 9 | 10 | let(:user) { create :user } 11 | let(:pricing_options) { Spree::Config.pricing_options_class.new } 12 | let(:query) { {} } 13 | 14 | context 'when products do not exist' do 15 | it { is_expected.to be_empty } 16 | end 17 | 18 | context 'when products exist' do 19 | let(:solidus_t_shirt) { create(:product, name: 'Solidus T-Shirt', id: 1, price: 19.99) } 20 | let(:ruby_mug) { create(:product, name: 'Ruby Mug', id: 2, price: 9.99) } 21 | let(:solidus_mug) { create(:product, name: 'Solidus Mug ', id: 3, price: 9.99) } 22 | let(:solidus_tote) { create(:product, name: 'Solidus Tote', id: 4, price: 15.99) } 23 | let!(:all_products) { [solidus_t_shirt, ruby_mug, solidus_mug, solidus_tote] } 24 | 25 | context 'when no query is passed' do 26 | it { is_expected.to match_array(all_products) } 27 | end 28 | 29 | context 'when a query is passed' do 30 | let(:taxonomy) { create(:taxonomy, name: 'Categories') } 31 | let(:t_shirts_taxon) { create(:taxon, name: 'T-Shirts', taxonomy: taxonomy, products: [solidus_t_shirt]) } 32 | let(:mugs_taxon) { create(:taxon, name: 'Mugs', taxonomy: taxonomy, products: [ruby_mug, solidus_mug]) } 33 | let(:totes_taxon) { create(:taxon, name: 'Totes', taxonomy: taxonomy, products: [solidus_tote]) } 34 | 35 | let(:search) { { price_range_any: ["Under $10.00", "$15.00 - $18.00"] } } 36 | let(:query) { { taxon: totes_taxon, keywords: "Solidus", search: search } } 37 | 38 | it { is_expected.to match_array([solidus_tote]) } 39 | 40 | context 'that contains page and/or per_page params' do 41 | let(:query) { { keywords: "Solidus", page: 2, per_page: 2 } } 42 | 43 | it 'ignores the page and per_page params' do 44 | expect(subject).to match_array([solidus_t_shirt, solidus_mug, solidus_tote]) 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/shipment/shipping_rates_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Shipment::ShippingRatesQuery do 6 | let(:shipment) { create(:shipment, shipping_rates: []) } 7 | let(:shipping_rates) { shipment.shipping_rates } 8 | 9 | context 'when shipment has shipping_rates' do 10 | it { expect(described_class.new(shipment: shipment).call.sync).to eq shipping_rates } 11 | end 12 | 13 | context 'when shipment does not have shipping_rates' do 14 | before { shipment.shipping_rates.destroy_all } 15 | 16 | it { expect(described_class.new(shipment: shipment).call.sync).to be_empty } 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/shipping_rate/shipping_method_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::ShippingRate::ShippingMethodQuery do 6 | let(:shipping_method) { create(:shipping_method) } 7 | 8 | let(:shipping_rate) { create(:shipping_rate, shipping_method: shipping_method) } 9 | 10 | it { expect(described_class.new(shipping_rate: shipping_rate).call.sync).to eq(shipping_method) } 11 | end 12 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/taxon/children_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Taxon::ChildrenQuery do 6 | it do 7 | taxonomy = create(:taxonomy) 8 | root = taxonomy.root 9 | children = [ 10 | create(:taxon, name: "Taxon one", parent: root), 11 | create(:taxon, name: "Taxon two", parent: root) 12 | ] 13 | expect(described_class.new(taxon: root).call.sync).to match_array(children) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/taxon/parent_taxon_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Taxon::ParentTaxonQuery do 6 | let(:parent_taxon) { create(:taxon) } 7 | let(:taxon) { create(:taxon, parent: parent_taxon) } 8 | 9 | it { expect(described_class.new(taxon: taxon).call.sync).to eq(parent_taxon) } 10 | end 11 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/taxonomies_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::TaxonomiesQuery do 6 | subject { described_class.new.call } 7 | 8 | let!(:taxonomies) { create_list(:taxonomy, 2) } 9 | 10 | it { is_expected.to match_array(taxonomies) } 11 | end 12 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/taxonomy/root_taxon_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Taxonomy::RootTaxonQuery do 6 | let(:taxonomy) { create(:taxonomy) } 7 | let(:root_taxon) { taxonomy.root } 8 | 9 | it { expect(described_class.new(taxonomy: taxonomy).call.sync).to eq(root_taxon) } 10 | end 11 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/taxonomy/taxons_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Taxonomy::TaxonsQuery do 6 | it do 7 | taxonomy = create(:taxonomy) 8 | root_taxon = taxonomy.root 9 | taxon = create(:taxon, taxonomy: taxonomy, parent: root_taxon) 10 | create(:taxon) 11 | 12 | expect(described_class.new(taxonomy: taxonomy).call.sync).to match_array([root_taxon, taxon]) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/variant/default_price_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Variant::DefaultPriceQuery do 6 | let(:variant) { create(:variant) } 7 | 8 | it { expect(described_class.new(variant: variant).call).to eq(variant.default_price) } 9 | end 10 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/variant/images_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Variant::ImagesQuery do 6 | let(:variant) { create(:variant) } 7 | 8 | let!(:images) { create_list(:image, 2, viewable: variant ) } 9 | 10 | it { expect(described_class.new(variant: variant).call.sync).to match_array(images) } 11 | end 12 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/variant/option_values_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Variant::OptionValuesQuery do 6 | let(:variant) { create(:base_variant) } 7 | 8 | let!(:option_values) { create_list(:option_value, 2, variants: [variant]) } 9 | 10 | it { expect(described_class.new(variant: variant).call.sync).to match_array(option_values) } 11 | end 12 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/queries/variant/prices_query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Queries::Variant::PricesQuery do 6 | let(:variant) { create(:variant) } 7 | 8 | let(:prices) { create_list(:price, 2, variant: variant) } 9 | 10 | let!(:variant_prices) { [variant.default_price] + prices } 11 | 12 | it { expect(described_class.new(variant: variant).call.sync).to match_array(variant_prices) } 13 | end 14 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/types/base/object_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Types::Base::Object do 6 | subject do 7 | Class.new(described_class) do 8 | graphql_name :test 9 | end 10 | end 11 | 12 | describe '.remove_field' do 13 | context 'when the field is defined' do 14 | before do 15 | subject.field :name, String, null: false 16 | end 17 | 18 | it 'removes the field' do 19 | expect { 20 | subject.remove_field :name 21 | }.to change { subject.own_fields['name'] }.to(nil) 22 | end 23 | end 24 | 25 | context 'when the field is not defined' do 26 | it 'raises an ArgumentError' do 27 | expect { 28 | subject.remove_field :name 29 | }.to raise_error(ArgumentError) 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/types/option_type_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Types::OptionType do 6 | it { expect(described_class.method_defined?(:option_values)).to be_truthy } 7 | end 8 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/types/option_value_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Types::OptionValue do 6 | let(:option_value) { create(:option_value) } 7 | let(:query_object) { spy(:query_object) } 8 | 9 | subject { described_class.send(:new, option_value, {}) } 10 | 11 | describe '#option_type' do 12 | before do 13 | allow(SolidusGraphqlApi::Queries::OptionValue::OptionTypeQuery). 14 | to receive(:new).with(option_value: option_value). 15 | and_return(query_object) 16 | end 17 | 18 | after { subject.option_type } 19 | 20 | it { expect(query_object).to receive(:call) } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/types/product_property_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Types::ProductProperty do 6 | let(:product_property) { double(:product_property) } 7 | let(:query_object) { spy(:query_object) } 8 | 9 | subject { described_class.send(:new, product_property, {}) } 10 | 11 | describe '#property' do 12 | let(:query_class) { SolidusGraphqlApi::Queries::ProductProperty::PropertyQuery } 13 | 14 | before { allow(query_class).to receive(:new).and_return(query_object) } 15 | 16 | after { subject.property } 17 | 18 | it { expect(query_class).to receive(:new).with(product_property: product_property) } 19 | 20 | it { expect(query_object).to receive(:call) } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/types/product_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Types::Product do 6 | let(:product) { create(:product) } 7 | let(:query_object) { spy(:query_object) } 8 | 9 | subject { described_class.send(:new, product, {}) } 10 | 11 | describe '#master_variant' do 12 | before do 13 | allow(SolidusGraphqlApi::Queries::Product::MasterVariantQuery).to receive(:new).and_return(query_object) 14 | end 15 | 16 | after { subject.master_variant } 17 | 18 | it { expect(SolidusGraphqlApi::Queries::Product::MasterVariantQuery).to receive(:new).with(product: product) } 19 | 20 | it { expect(query_object).to receive(:call) } 21 | end 22 | 23 | describe '#option_types' do 24 | before do 25 | allow(SolidusGraphqlApi::Queries::Product::OptionTypesQuery).to receive(:new).and_return(query_object) 26 | end 27 | 28 | after { subject.option_types } 29 | 30 | it { expect(SolidusGraphqlApi::Queries::Product::OptionTypesQuery).to receive(:new).with(product: product) } 31 | 32 | it { expect(query_object).to receive(:call) } 33 | end 34 | 35 | describe '#product_properties' do 36 | before do 37 | allow(SolidusGraphqlApi::Queries::Product::ProductPropertiesQuery). 38 | to receive(:new).with(product: product). 39 | and_return(query_object) 40 | end 41 | 42 | after { subject.product_properties } 43 | 44 | it { expect(query_object).to receive(:call) } 45 | end 46 | 47 | describe '#variants' do 48 | before do 49 | allow(SolidusGraphqlApi::Queries::Product::VariantsQuery).to receive(:new).and_return(query_object) 50 | end 51 | 52 | after { subject.variants } 53 | 54 | it { expect(SolidusGraphqlApi::Queries::Product::VariantsQuery).to receive(:new).with(product: product) } 55 | 56 | it { expect(query_object).to receive(:call) } 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/types/query_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Types::Query do 6 | subject { described_class.send(:new, {}, {}) } 7 | 8 | it { expect(described_class.method_defined?(:taxonomies)).to be_truthy } 9 | 10 | describe '#products' do 11 | let(:query_object) { spy(:query_object) } 12 | 13 | before { allow(SolidusGraphqlApi::Queries::ProductsQuery).to receive(:new).and_return(query_object) } 14 | 15 | after { subject.products } 16 | 17 | it { expect(SolidusGraphqlApi::Queries::ProductsQuery).to receive(:new) } 18 | 19 | it { expect(query_object).to receive(:call) } 20 | end 21 | 22 | describe '#product_by_slug' do 23 | let(:query_object) { spy(:query_object) } 24 | 25 | let(:slug) { 'slug' } 26 | 27 | before { 28 | allow(SolidusGraphqlApi::Queries::ProductBySlugQuery).to receive(:new).with(no_args).and_return(query_object) 29 | } 30 | 31 | after { subject.product_by_slug(slug: slug) } 32 | 33 | it { expect(query_object).to receive(:call).with(slug: slug) } 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/types/shipping_rate_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Types::ShippingRate do 6 | let(:shipping_rate) { create(:shipping_rate) } 7 | let(:query_object) { spy(:query_object) } 8 | 9 | subject { described_class.send(:new, shipping_rate, {}) } 10 | 11 | describe '#shipping_method' do 12 | before do 13 | allow(SolidusGraphqlApi::Queries::ShippingRate::ShippingMethodQuery). 14 | to receive(:new).with(shipping_rate: shipping_rate). 15 | and_return(query_object) 16 | end 17 | 18 | after { subject.shipping_method } 19 | 20 | it { expect(query_object).to receive(:call) } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/lib/solidus_graphql_api/types/variant_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe SolidusGraphqlApi::Types::Variant do 6 | let(:variant) { create(:variant) } 7 | let(:query_object) { spy(:query_object) } 8 | 9 | subject { described_class.send(:new, variant, {}) } 10 | 11 | describe '#default_price' do 12 | before do 13 | allow(SolidusGraphqlApi::Queries::Variant::DefaultPriceQuery).to receive(:new).and_return(query_object) 14 | end 15 | 16 | after { subject.default_price } 17 | 18 | it { expect(SolidusGraphqlApi::Queries::Variant::DefaultPriceQuery).to receive(:new).with(variant: variant) } 19 | 20 | it { expect(query_object).to receive(:call) } 21 | end 22 | 23 | describe '#images' do 24 | before do 25 | allow(SolidusGraphqlApi::Queries::Variant::ImagesQuery).to receive(:new).and_return(query_object) 26 | end 27 | 28 | after { subject.images } 29 | 30 | it { expect(SolidusGraphqlApi::Queries::Variant::ImagesQuery).to receive(:new).with(variant: variant) } 31 | 32 | it { expect(query_object).to receive(:call) } 33 | end 34 | 35 | describe '#option_values' do 36 | before do 37 | allow(SolidusGraphqlApi::Queries::Variant::OptionValuesQuery).to receive(:new).and_return(query_object) 38 | end 39 | 40 | after { subject.option_values } 41 | 42 | it { expect(SolidusGraphqlApi::Queries::Variant::OptionValuesQuery).to receive(:new).with(variant: variant) } 43 | 44 | it { expect(query_object).to receive(:call) } 45 | end 46 | 47 | describe '#prices' do 48 | before do 49 | allow(SolidusGraphqlApi::Queries::Variant::PricesQuery).to receive(:new).and_return(query_object) 50 | end 51 | 52 | after { subject.prices } 53 | 54 | it { expect(SolidusGraphqlApi::Queries::Variant::PricesQuery).to receive(:new).with(variant: variant) } 55 | 56 | it { expect(query_object).to receive(:call) } 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/requests/spree/graphql_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Spree::GraphqlController do 6 | let(:context) { Hash[] } 7 | 8 | before { allow_any_instance_of(SolidusGraphqlApi::Context).to receive(:to_h).and_return(context) } 9 | 10 | it 'passes the right context to the schema' do 11 | expect(SolidusGraphqlApi::Schema).to receive(:execute).with( 12 | nil, hash_including(context: context) 13 | ) 14 | 15 | post '/graphql', headers: headers 16 | end 17 | 18 | it 'can return blob URLs on disk storage' do 19 | product = create(:product) 20 | product.images.create(attributes_for(:image)) 21 | 22 | post '/graphql', 23 | params: { 24 | query: "{ productBySlug(slug: \"#{product.slug}\") { masterVariant { images { nodes { smallUrl } } } } }" 25 | }, 26 | headers: headers 27 | 28 | expect(JSON.parse(response.body).dig(*%w[data productBySlug masterVariant images nodes])[0]['smallUrl']).to match('http://') 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/support/graphql/mutations/add_addresses_to_checkout.gql: -------------------------------------------------------------------------------- 1 | mutation ($input: AddAddressesToCheckoutInput!) { 2 | addAddressesToCheckout(input: $input) { 3 | order { 4 | email 5 | number 6 | state 7 | billingAddress { 8 | name 9 | } 10 | shippingAddress { 11 | name 12 | } 13 | }, 14 | errors { 15 | path 16 | message 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /spec/support/graphql/mutations/add_payment_to_checkout.gql: -------------------------------------------------------------------------------- 1 | mutation ($input: AddPaymentToCheckoutInput!) { 2 | addPaymentToCheckout(input: $input) { 3 | order { 4 | email 5 | number 6 | state 7 | }, 8 | errors { 9 | path 10 | message 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spec/support/graphql/mutations/add_to_cart.gql: -------------------------------------------------------------------------------- 1 | mutation ($input: AddToCartInput!){ 2 | addToCart(input: $input) { 3 | order { 4 | number 5 | state 6 | lineItems { 7 | nodes { 8 | id 9 | quantity 10 | variant { 11 | id 12 | } 13 | } 14 | } 15 | }, 16 | errors { 17 | path 18 | message 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spec/support/graphql/mutations/advance_checkout.gql: -------------------------------------------------------------------------------- 1 | mutation ($input: AdvanceCheckoutInput!) { 2 | advanceCheckout(input: $input) { 3 | order { 4 | email 5 | number 6 | state 7 | }, 8 | errors { 9 | path 10 | message 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spec/support/graphql/mutations/apply_coupon_code.gql: -------------------------------------------------------------------------------- 1 | mutation ($input: ApplyCouponCodeInput!) { 2 | applyCouponCode(input: $input) { 3 | order { 4 | email 5 | number 6 | adjustmentTotal 7 | }, 8 | errors { 9 | path 10 | message 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spec/support/graphql/mutations/complete_checkout.gql: -------------------------------------------------------------------------------- 1 | mutation ($input: CompleteCheckoutInput!) { 2 | completeCheckout(input: $input) { 3 | order { 4 | email 5 | number 6 | state 7 | }, 8 | errors { 9 | path 10 | message 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spec/support/graphql/mutations/create_order.gql: -------------------------------------------------------------------------------- 1 | mutation($input: CreateOrderInput!) { 2 | createOrder(input: $input) { 3 | order { 4 | number 5 | state 6 | } 7 | errors { 8 | message 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spec/support/graphql/mutations/empty_cart.gql: -------------------------------------------------------------------------------- 1 | mutation ($input: EmptyCartInput!){ 2 | emptyCart(input: $input) { 3 | order { 4 | number 5 | state 6 | lineItems { 7 | nodes { 8 | id 9 | } 10 | } 11 | }, 12 | errors { 13 | path 14 | message 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spec/support/graphql/mutations/mark_default_ship_address.gql: -------------------------------------------------------------------------------- 1 | mutation ($input: MarkDefaultShipAddressInput!) { 2 | markDefaultShipAddress(input: $input) { 3 | user { 4 | shipAddress { 5 | id 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /spec/support/graphql/mutations/next_checkout_state.gql: -------------------------------------------------------------------------------- 1 | mutation ($input: NextCheckoutStateInput!) { 2 | nextCheckoutState(input: $input) { 3 | order { 4 | email 5 | number 6 | state 7 | }, 8 | errors { 9 | path 10 | message 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spec/support/graphql/mutations/remove_from_address_book.gql: -------------------------------------------------------------------------------- 1 | mutation ($input: RemoveFromAddressBookInput!) { 2 | removeFromAddressBook(input: $input) { 3 | user { 4 | shipAddress { 5 | id 6 | } 7 | addresses { 8 | nodes { 9 | id 10 | } 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spec/support/graphql/mutations/remove_from_cart.gql: -------------------------------------------------------------------------------- 1 | mutation ($input: RemoveFromCartInput!){ 2 | removeFromCart(input: $input) { 3 | order { 4 | number 5 | state 6 | lineItems { 7 | nodes { 8 | id 9 | } 10 | } 11 | }, 12 | errors { 13 | path 14 | message 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spec/support/graphql/mutations/save_in_address_book.gql: -------------------------------------------------------------------------------- 1 | mutation ($input: SaveInAddressBookInput!) { 2 | saveInAddressBook(input: $input) { 3 | user { 4 | email 5 | shipAddress { 6 | address1 7 | address2 8 | alternativePhone 9 | city 10 | company 11 | country { 12 | id 13 | name 14 | } 15 | createdAt 16 | name 17 | phone 18 | stateName 19 | state { 20 | id 21 | name 22 | } 23 | updatedAt 24 | zipcode 25 | } 26 | billAddress { 27 | name 28 | } 29 | addresses { 30 | nodes { 31 | address1 32 | address2 33 | alternativePhone 34 | city 35 | company 36 | country { 37 | id 38 | name 39 | } 40 | createdAt 41 | name 42 | phone 43 | stateName 44 | state { 45 | id 46 | name 47 | } 48 | updatedAt 49 | zipcode 50 | } 51 | } 52 | } 53 | errors { 54 | path 55 | message 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /spec/support/graphql/mutations/select_shipping_rate.gql: -------------------------------------------------------------------------------- 1 | mutation ($input: SelectShippingRateInput!) { 2 | selectShippingRate(input: $input) { 3 | order { 4 | number 5 | state 6 | shipments { 7 | nodes { 8 | id 9 | shippingRates { 10 | nodes { 11 | id 12 | selected 13 | } 14 | } 15 | } 16 | } 17 | } 18 | errors { 19 | path 20 | message 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spec/support/graphql/mutations/set_order_email.gql: -------------------------------------------------------------------------------- 1 | mutation ($input: SetOrderEmailInput!){ 2 | setOrderEmail(input: $input) { 3 | order { 4 | number 5 | state 6 | email 7 | }, 8 | errors { 9 | path 10 | message 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spec/support/graphql/mutations/update_cart_quantity.gql: -------------------------------------------------------------------------------- 1 | mutation ($input: UpdateCartQuantityInput!){ 2 | updateCartQuantity(input: $input) { 3 | order { 4 | number 5 | state 6 | lineItems { 7 | nodes { 8 | id 9 | quantity 10 | } 11 | } 12 | }, 13 | errors { 14 | path 15 | message 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spec/support/graphql/queries/completed_orders.gql: -------------------------------------------------------------------------------- 1 | query { 2 | completedOrders { 3 | nodes { 4 | id 5 | additionalTaxTotal 6 | adjustmentTotal 7 | approvedAt 8 | canceledAt 9 | completedAt 10 | confirmationDelivered 11 | createdAt 12 | currency 13 | email 14 | guestToken 15 | includedTaxTotal 16 | itemTotal 17 | number 18 | paymentState 19 | paymentTotal 20 | promoTotal 21 | shipmentState 22 | shipmentTotal 23 | specialInstructions 24 | state 25 | total 26 | updatedAt 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spec/support/graphql/queries/completed_orders/adjustments.gql: -------------------------------------------------------------------------------- 1 | query { 2 | completedOrders { 3 | nodes { 4 | id 5 | adjustments { 6 | nodes { 7 | __typename 8 | amount 9 | createdAt 10 | eligible 11 | label 12 | updatedAt 13 | ... on PromotionAdjustment { 14 | id 15 | promotionCode 16 | } 17 | ... on TaxAdjustment { 18 | id 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /spec/support/graphql/queries/completed_orders/available_payment_methods.gql: -------------------------------------------------------------------------------- 1 | query { 2 | completedOrders { 3 | nodes { 4 | id 5 | availablePaymentMethods { 6 | createdAt 7 | description 8 | id 9 | name 10 | position 11 | updatedAt 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /spec/support/graphql/queries/completed_orders/billing_address.gql: -------------------------------------------------------------------------------- 1 | query { 2 | completedOrders { 3 | nodes { 4 | id 5 | billingAddress { 6 | id 7 | address1 8 | address2 9 | alternativePhone 10 | city 11 | company 12 | country { 13 | id 14 | createdAt 15 | iso 16 | iso3 17 | isoName 18 | name 19 | numcode 20 | updatedAt 21 | } 22 | id 23 | createdAt 24 | name 25 | phone 26 | state { 27 | id 28 | abbr 29 | name 30 | createdAt 31 | updatedAt 32 | } 33 | stateName 34 | updatedAt 35 | zipcode 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spec/support/graphql/queries/completed_orders/line_items.gql: -------------------------------------------------------------------------------- 1 | query { 2 | completedOrders { 3 | nodes { 4 | id 5 | lineItems { 6 | nodes { 7 | id 8 | additionalTaxTotal 9 | adjustmentTotal 10 | amount 11 | createdAt 12 | currency 13 | hasSufficientStock 14 | includedTaxTotal 15 | price 16 | promoTotal 17 | quantity 18 | updatedAt 19 | } 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spec/support/graphql/queries/completed_orders/payments.gql: -------------------------------------------------------------------------------- 1 | query { 2 | completedOrders { 3 | nodes { 4 | id 5 | payments { 6 | amount 7 | state 8 | createdAt 9 | id 10 | paymentSource { 11 | ... on CreditCard { 12 | ccType 13 | lastDigits 14 | month 15 | name 16 | year 17 | } 18 | } 19 | updatedAt 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spec/support/graphql/queries/completed_orders/shipping_address.gql: -------------------------------------------------------------------------------- 1 | query { 2 | completedOrders { 3 | nodes { 4 | id 5 | shippingAddress { 6 | id 7 | address1 8 | address2 9 | alternativePhone 10 | city 11 | company 12 | country { 13 | id 14 | createdAt 15 | iso 16 | iso3 17 | isoName 18 | name 19 | numcode 20 | updatedAt 21 | } 22 | id 23 | createdAt 24 | name 25 | phone 26 | state { 27 | id 28 | abbr 29 | name 30 | createdAt 31 | updatedAt 32 | } 33 | stateName 34 | updatedAt 35 | zipcode 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spec/support/graphql/queries/countries.gql: -------------------------------------------------------------------------------- 1 | query { 2 | countries { 3 | nodes { 4 | createdAt 5 | id 6 | iso 7 | iso3 8 | isoName 9 | name 10 | numcode 11 | states { 12 | nodes { 13 | abbr 14 | createdAt 15 | id 16 | name 17 | updatedAt 18 | } 19 | } 20 | statesRequired 21 | updatedAt 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /spec/support/graphql/queries/current_order.gql: -------------------------------------------------------------------------------- 1 | query { 2 | currentOrder { 3 | id 4 | number 5 | shipments { 6 | nodes { 7 | createdAt 8 | number 9 | shippedAt 10 | shippingRates { 11 | nodes { 12 | cost 13 | createdAt 14 | currency 15 | selected 16 | updatedAt 17 | } 18 | } 19 | manifest { 20 | variant { 21 | sku 22 | } 23 | quantity 24 | } 25 | state 26 | tracking 27 | trackingUrl 28 | updatedAt 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /spec/support/graphql/queries/current_store.gql: -------------------------------------------------------------------------------- 1 | query { 2 | currentStore { 3 | cartTaxCountryIso 4 | code 5 | createdAt 6 | currencies { 7 | nodes { 8 | htmlEntity 9 | isoCode 10 | name 11 | symbol 12 | } 13 | } 14 | default 15 | defaultCurrency 16 | mailFromAddress 17 | metaDescription 18 | metaKeywords 19 | name 20 | seoTitle 21 | updatedAt 22 | url 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /spec/support/graphql/queries/product_by_slug.gql: -------------------------------------------------------------------------------- 1 | query ($slug: String!) { 2 | productBySlug(slug: $slug) { 3 | createdAt 4 | description 5 | id 6 | masterVariant { 7 | createdAt 8 | defaultPrice { 9 | amount 10 | countryIso 11 | createdAt 12 | currency { 13 | htmlEntity 14 | isoCode 15 | name 16 | symbol 17 | } 18 | displayAmount 19 | displayCountry 20 | id 21 | updatedAt 22 | } 23 | depth 24 | height 25 | id 26 | isMaster 27 | optionValues { 28 | nodes { 29 | createdAt 30 | id 31 | name 32 | position 33 | presentation 34 | updatedAt 35 | } 36 | } 37 | position 38 | prices { 39 | nodes { 40 | amount 41 | countryIso 42 | createdAt 43 | currency { 44 | htmlEntity 45 | isoCode 46 | name 47 | symbol 48 | } 49 | displayAmount 50 | displayCountry 51 | id 52 | updatedAt 53 | } 54 | } 55 | sku 56 | updatedAt 57 | weight 58 | width 59 | } 60 | metaDescription 61 | metaKeywords 62 | metaTitle 63 | name 64 | optionTypes { 65 | nodes { 66 | createdAt 67 | id 68 | name 69 | optionValues { 70 | nodes { 71 | createdAt 72 | id 73 | name 74 | position 75 | presentation 76 | updatedAt 77 | } 78 | } 79 | position 80 | presentation 81 | updatedAt 82 | } 83 | } 84 | productProperties { 85 | nodes { 86 | createdAt 87 | position 88 | property { 89 | createdAt 90 | name 91 | presentation 92 | updatedAt 93 | } 94 | updatedAt 95 | value 96 | } 97 | } 98 | slug 99 | updatedAt 100 | variants { 101 | nodes { 102 | id 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /spec/support/graphql/queries/products.gql: -------------------------------------------------------------------------------- 1 | query ($query: ProductsQueryInput) { 2 | products(query: $query) { 3 | nodes { 4 | id 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /spec/support/graphql/queries/taxonomies.gql: -------------------------------------------------------------------------------- 1 | query { 2 | taxonomies { 3 | nodes { 4 | createdAt 5 | id 6 | name 7 | updatedAt 8 | rootTaxon { 9 | children { 10 | nodes { 11 | children { 12 | nodes { 13 | id 14 | } 15 | } 16 | createdAt 17 | description 18 | iconUrl 19 | id 20 | metaDescription 21 | metaKeywords 22 | metaTitle 23 | name 24 | parentTaxon { 25 | id 26 | } 27 | permalink 28 | updatedAt 29 | } 30 | } 31 | createdAt 32 | description 33 | iconUrl 34 | id 35 | metaDescription 36 | metaKeywords 37 | metaTitle 38 | name 39 | parentTaxon { 40 | id 41 | } 42 | permalink 43 | updatedAt 44 | } 45 | taxons { 46 | nodes { 47 | children { 48 | nodes { 49 | createdAt 50 | description 51 | iconUrl 52 | id 53 | metaDescription 54 | metaKeywords 55 | metaTitle 56 | name 57 | parentTaxon { 58 | id 59 | } 60 | permalink 61 | updatedAt 62 | } 63 | } 64 | createdAt 65 | description 66 | iconUrl 67 | id 68 | metaDescription 69 | metaKeywords 70 | metaTitle 71 | name 72 | parentTaxon { 73 | id 74 | } 75 | permalink 76 | updatedAt 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /spec/support/graphql/responses/completed_orders.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "completedOrders": { 4 | "nodes": [ 5 | { 6 | "id": 1, 7 | "additionalTaxTotal": "0.0", 8 | "adjustmentTotal": "0.0", 9 | "approvedAt": null, 10 | "canceledAt": null, 11 | "completedAt": "2012-12-21T12:00:00Z", 12 | "confirmationDelivered": false, 13 | "createdAt": "2012-12-21T12:00:00Z", 14 | "currency": "USD", 15 | "email": "email1@example.com", 16 | "guestToken": "fake guest token 1", 17 | "includedTaxTotal": "0.0", 18 | "itemTotal": "10.0", 19 | "number": "fake order number 1", 20 | "paymentState": "balance_due", 21 | "paymentTotal": "0.0", 22 | "promoTotal": "0.0", 23 | "shipmentState": "pending", 24 | "shipmentTotal": "100.0", 25 | "specialInstructions": null, 26 | "state": "complete", 27 | "total": "110.0", 28 | "updatedAt": "2012-12-21T12:00:00Z" 29 | }, 30 | { 31 | "id": 2, 32 | "additionalTaxTotal": "0.0", 33 | "adjustmentTotal": "0.0", 34 | "approvedAt": null, 35 | "canceledAt": null, 36 | "completedAt": "2012-12-21T12:00:00Z", 37 | "confirmationDelivered": false, 38 | "createdAt": "2012-12-21T12:00:00Z", 39 | "currency": "USD", 40 | "email": "email1@example.com", 41 | "guestToken": "fake guest token 2", 42 | "includedTaxTotal": "0.0", 43 | "itemTotal": "10.0", 44 | "number": "fake order number 2", 45 | "paymentState": "balance_due", 46 | "paymentTotal": "0.0", 47 | "promoTotal": "0.0", 48 | "shipmentState": "pending", 49 | "shipmentTotal": "100.0", 50 | "specialInstructions": null, 51 | "state": "complete", 52 | "total": "110.0", 53 | "updatedAt": "2012-12-21T12:00:00Z" 54 | } 55 | ] 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /spec/support/graphql/responses/completed_orders/adjustments.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "completedOrders": { 4 | "nodes": [ 5 | { 6 | "id": 1, 7 | "adjustments": { 8 | "nodes": [ 9 | { 10 | "__typename": "TaxAdjustment", 11 | "amount": "100.0", 12 | "createdAt": "2012-12-21T12:00:00Z", 13 | "eligible": true, 14 | "label": "Shipping", 15 | "updatedAt": "2012-12-21T12:00:00Z", 16 | "id": <%= adjustments[0].id %> 17 | }, 18 | { 19 | "__typename": "PromotionAdjustment", 20 | "amount": "-10.0", 21 | "createdAt": "2012-12-21T12:00:00Z", 22 | "eligible": true, 23 | "label": "Promotion (Promo)", 24 | "updatedAt": "2012-12-21T12:00:00Z", 25 | "id": <%= adjustments[1].id %>, 26 | "promotionCode": "solidus" 27 | } 28 | ] 29 | } 30 | } 31 | ] 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /spec/support/graphql/responses/completed_orders/available_payment_methods.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "completedOrders": { 4 | "nodes": [ 5 | { 6 | "id": 1, 7 | "availablePaymentMethods": [ 8 | { 9 | "createdAt": "2012-12-21T12:00:00Z", 10 | "description": null, 11 | "id": <%= available_payment_methods[0].id %>, 12 | "name": "Credit Card", 13 | "position": "1", 14 | "updatedAt": "2012-12-21T12:00:00Z" 15 | }, 16 | { 17 | "createdAt": "2012-12-21T12:00:00Z", 18 | "description": null, 19 | "id": <%= available_payment_methods[1].id %>, 20 | "name": "Credit Card", 21 | "position": "2", 22 | "updatedAt": "2012-12-21T12:00:00Z" 23 | } 24 | ] 25 | } 26 | ] 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spec/support/graphql/responses/completed_orders/line_items.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "completedOrders": { 4 | "nodes": [ 5 | { 6 | "id": 1, 7 | "lineItems": { 8 | "nodes": [ 9 | { 10 | "id": <%= line_items[0].id %>, 11 | "additionalTaxTotal": 0.0, 12 | "adjustmentTotal": 0.0, 13 | "amount": 10.0, 14 | "createdAt": "2012-12-21T12:00:00Z", 15 | "currency": "USD", 16 | "hasSufficientStock": true, 17 | "includedTaxTotal": 0.0, 18 | "price": 10.0, 19 | "promoTotal": 0.0, 20 | "quantity": 1, 21 | "updatedAt": "2012-12-21T12:00:00Z" 22 | }, 23 | { 24 | "id": <%= line_items[1].id %>, 25 | "additionalTaxTotal": 0.0, 26 | "adjustmentTotal": 0.0, 27 | "amount": 10.0, 28 | "createdAt": "2012-12-21T12:00:00Z", 29 | "currency": "USD", 30 | "hasSufficientStock": true, 31 | "includedTaxTotal": 0.0, 32 | "price": 10.0, 33 | "promoTotal": 0.0, 34 | "quantity": 1, 35 | "updatedAt": "2012-12-21T12:00:00Z" 36 | } 37 | ] 38 | } 39 | } 40 | ] 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /spec/support/graphql/responses/completed_orders/payments.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "completedOrders": { 4 | "nodes": [ 5 | { 6 | "id": 1, 7 | "payments": [ 8 | { 9 | "amount": "110.0", 10 | "state": "pending", 11 | "createdAt": "2012-12-21T12:00:00Z", 12 | "id": <%= payments[0].id %>, 13 | "paymentSource": { 14 | "ccType": null, 15 | "lastDigits": "1111", 16 | "month": "12", 17 | "name": "Spree Commerce", 18 | "year": "2013" 19 | }, 20 | "updatedAt": "2012-12-21T12:00:00Z" 21 | } 22 | ] 23 | } 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spec/support/graphql/responses/countries.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "countries": { 4 | "nodes": [ 5 | { 6 | "createdAt": "2012-12-21T12:00:00Z", 7 | "id": 1, 8 | "iso": "US", 9 | "iso3": "USA", 10 | "isoName": "UNITED STATES", 11 | "name": "United States", 12 | "numcode": 840, 13 | "states": { 14 | "nodes": [ 15 | { 16 | "abbr": "AL", 17 | "createdAt": "2012-12-21T12:00:00Z", 18 | "id": 1, 19 | "name": "Alabama", 20 | "updatedAt": "2012-12-21T12:00:00Z" 21 | }, 22 | { 23 | "abbr": "CA", 24 | "createdAt": "2012-12-21T12:00:00Z", 25 | "id": 2, 26 | "name": "California", 27 | "updatedAt": "2012-12-21T12:00:00Z" 28 | } 29 | ] 30 | }, 31 | "statesRequired": false, 32 | "updatedAt": "2012-12-21T12:00:00Z" 33 | }, 34 | { 35 | "createdAt": "2012-12-21T12:00:00Z", 36 | "id": 2, 37 | "iso": "IT", 38 | "iso3": "ITA", 39 | "isoName": "ITALY", 40 | "name": "Italy", 41 | "numcode": 380, 42 | "states": { 43 | "nodes": [] 44 | }, 45 | "statesRequired": false, 46 | "updatedAt": "2012-12-21T12:00:00Z" 47 | } 48 | ] 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /spec/support/graphql/responses/current_order.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "currentOrder": { 4 | "id": 1, 5 | "number": "fake number", 6 | "shipments": { 7 | "nodes": [{ 8 | "createdAt": "2012-12-21T12:00:00Z", 9 | "number": "<%= shipment_number %>", 10 | "shippedAt": null, 11 | "shippingRates": { 12 | "nodes": [{ 13 | "cost": "100.0", 14 | "createdAt": "2012-12-21T12:00:00Z", 15 | "currency": "USD", 16 | "selected": true, 17 | "updatedAt": "2012-12-21T12:00:00Z" 18 | }] 19 | }, 20 | "manifest": [ 21 | { 22 | "variant": { 23 | "sku": "<%= variant_sku %>" 24 | }, 25 | "quantity": 1 26 | } 27 | ], 28 | "state": "pending", 29 | "tracking": "U10000", 30 | "trackingUrl": null, 31 | "updatedAt": "2012-12-21T12:00:00Z" 32 | }] 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /spec/support/graphql/responses/current_store.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "currentStore": { 4 | "cartTaxCountryIso": "US", 5 | "code": "solidus", 6 | "createdAt": "2012-12-21T12:00:00Z", 7 | "currencies": { 8 | "nodes": [ 9 | { 10 | "htmlEntity": "$", 11 | "isoCode": "USD", 12 | "name": "United States Dollar", 13 | "symbol": "$" 14 | }, 15 | { 16 | "htmlEntity": "€", 17 | "isoCode": "EUR", 18 | "name": "Euro", 19 | "symbol": "€" 20 | } 21 | ] 22 | }, 23 | "default": true, 24 | "defaultCurrency": "USD", 25 | "mailFromAddress": "solidus_graphql_api@example.org", 26 | "metaDescription": "store description", 27 | "metaKeywords": "store, metaKeywords", 28 | "name": "Solidus Test Store", 29 | "seoTitle": "Store Title", 30 | "updatedAt": "2012-12-21T12:00:00Z", 31 | "url": "www.example.com" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /spec/support/helpers/active_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Helpers 4 | module ActiveRecord 5 | def run_migrations(&block) 6 | migration = Class.new(::ActiveRecord::Migration[6.1]).tap do |klass| 7 | klass.define_method(:up, &block) 8 | end 9 | ::ActiveRecord::Migration.suppress_messages { migration.migrate(:up) } 10 | end 11 | 12 | def create_model(name, &block) 13 | stub_const(name, Class.new(ApplicationRecord)).tap do |klass| 14 | klass.class_eval(&block) if block_given? 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/support/helpers/graphql.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Helpers 4 | module Graphql 5 | def execute(path, query_or_mutation, variables: {}, context: {}, decode_ids: true, object_class: Hash) 6 | result = SolidusGraphqlApi::Schema.execute( 7 | File.read(File.join(path, "#{query_or_mutation}.gql")), 8 | variables: variables, 9 | context: context 10 | ) 11 | 12 | result = decode_field_ids(result.to_h) if decode_ids 13 | 14 | JSON.parse(result.to_json, object_class: object_class, symbolize_names: true) 15 | end 16 | 17 | def execute_query(*args, **kwargs) 18 | execute(RSpec.configuration.graphql_queries_dir, *args, **kwargs) 19 | end 20 | 21 | def execute_mutation(*args, **kwargs) 22 | execute(RSpec.configuration.graphql_mutations_dir, *args, **kwargs) 23 | end 24 | 25 | def decode_field_ids(field) 26 | field.each do |field_name, field_value| 27 | case field_value 28 | when String 29 | if field_name == "id" 30 | field[field_name] = GraphQL::Schema::UniqueWithinType.decode(field_value).last.to_i 31 | end 32 | when Hash 33 | decode_field_ids field_value 34 | when Array 35 | field_value.flatten.each do |child_field| 36 | decode_field_ids(child_field) if child_field.is_a?(Hash) 37 | end 38 | end 39 | end 40 | field 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/support/matchers/graphql.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Matchers 4 | module Graphql 5 | extend RSpec::Matchers::DSL 6 | 7 | matcher :match_response do |query_response_file| 8 | match do |actual| 9 | query_response_path = File.join(RSpec.configuration.graphql_responses_dir, "#{query_response_file}.json.erb") 10 | template = File.read(query_response_path) 11 | json_response = ERB.new(template).result_with_hash(@variables || {}) 12 | 13 | @expected = JSON.parse(json_response, object_class: @object_class || Hash, symbolize_names: true) 14 | 15 | # Lets the failure message have a nice diff 16 | @expected = JSON.pretty_generate @expected 17 | @actual = JSON.pretty_generate actual 18 | 19 | @actual == @expected 20 | end 21 | 22 | chain :with_args do |variables| 23 | @variables = variables 24 | end 25 | 26 | chain :as do |object_class| 27 | @object_class = object_class 28 | end 29 | 30 | diffable 31 | 32 | attr_reader :expected 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/support/shared_contexts/graphql_query_subject.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.shared_context "graphql query subject", type: :graphql_query do 4 | subject(:query_response) do |example| 5 | execute_query( 6 | try(:query) || example.metadata[:query], 7 | variables: try(:query_variables) || example.metadata[:query_variables], 8 | context: try(:query_context) || example.metadata[:query_context] 9 | ) 10 | end 11 | end 12 | 13 | RSpec.shared_context "graphql mutation subject", type: :graphql_mutation do 14 | subject(:mutation_response) do |example| 15 | execute_mutation( 16 | try(:mutation) || example.metadata[:mutation], 17 | variables: try(:mutation_variables) || example.metadata[:mutation_variables], 18 | context: try(:mutation_context) || example.metadata[:mutation_context] 19 | ) 20 | end 21 | end 22 | --------------------------------------------------------------------------------