add create sell and buy crypto order mutations
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
module Inputs
|
||||||
|
class CreateBuyCryptoOrderAttributesInput < Types::BaseInputObject
|
||||||
|
argument :currency_id, ID, required: true
|
||||||
|
argument :amount_cents, Integer, "Amount to be paid", required: true
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
module Inputs
|
||||||
|
class CreateSellCryptoOrderAttributesInput < Types::BaseInputObject
|
||||||
|
argument :currency_id, ID, required: true
|
||||||
|
argument :amount, String, "Amount to be paid", required: true
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -6,8 +6,16 @@ module Mutations
|
|||||||
input_object_class Types::BaseInputObject
|
input_object_class Types::BaseInputObject
|
||||||
object_class Types::BaseObject
|
object_class Types::BaseObject
|
||||||
|
|
||||||
field :errors, [String],
|
field :errors, [Types::RecordInvalidType],
|
||||||
null: true,
|
null: true,
|
||||||
description: "Errors encountered during execution of the mutation."
|
description: "Errors encountered during execution of the mutation."
|
||||||
|
|
||||||
|
def current_user
|
||||||
|
context[:current_user]
|
||||||
|
end
|
||||||
|
|
||||||
|
def decode_id(encoded_id)
|
||||||
|
GraphQL::Schema::UniqueWithinType.decode(encoded_id).last
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
26
app/graphql/mutations/create_buy_crypto_order.rb
Normal file
26
app/graphql/mutations/create_buy_crypto_order.rb
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
module Mutations
|
||||||
|
class CreateBuyCryptoOrder < BaseMutation
|
||||||
|
field :order, Types::BuyCryptoOrderType, null: true
|
||||||
|
|
||||||
|
argument :order, Inputs::CreateBuyCryptoOrderAttributesInput, required: true
|
||||||
|
|
||||||
|
def resolve(order:)
|
||||||
|
currency_id = decode_id(order[:currency_id])
|
||||||
|
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
current_user.fiat_balance.withdrawal!(order[:amount_cents])
|
||||||
|
|
||||||
|
record = BuyCryptoOrder.create!(
|
||||||
|
paid_amount_cents: order[:amount_cents],
|
||||||
|
currency_id: currency_id,
|
||||||
|
user_id: current_user.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
{ order: record }
|
||||||
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
|
{ errors: Resolvers::ModelErrors.from_active_record_model(e.record) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
28
app/graphql/mutations/create_sell_crypto_order.rb
Normal file
28
app/graphql/mutations/create_sell_crypto_order.rb
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
module Mutations
|
||||||
|
class CreateSellCryptoOrder < BaseMutation
|
||||||
|
field :order, Types::SellCryptoOrderType, null: true
|
||||||
|
|
||||||
|
argument :order, Inputs::CreateSellCryptoOrderAttributesInput, required: true
|
||||||
|
|
||||||
|
def resolve(order:)
|
||||||
|
currency_id = decode_id(order[:currency_id])
|
||||||
|
amount = BigDecimal(order[:amount])
|
||||||
|
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
current_user.balances.find_by!(currency_id: currency_id)
|
||||||
|
.withdrawal!(amount)
|
||||||
|
|
||||||
|
record = SellCryptoOrder.create(
|
||||||
|
paid_amount: amount,
|
||||||
|
currency_id: currency_id,
|
||||||
|
user_id: current_user.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
{ order: record }
|
||||||
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
|
{ errors: Resolvers::ModelErrors.from_active_record_model(e.record) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
39
app/graphql/resolvers/model_errors.rb
Normal file
39
app/graphql/resolvers/model_errors.rb
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
module Resolvers
|
||||||
|
class ModelErrors
|
||||||
|
attr_reader :full_messages, :field_name, :messages, :path
|
||||||
|
|
||||||
|
def initialize(args)
|
||||||
|
@full_messages = args[:full_messages]
|
||||||
|
@field_name = args[:field_name]
|
||||||
|
@messages = args[:messages]
|
||||||
|
@path = args[:path]
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_active_record_model(model)
|
||||||
|
return if model&.errors.blank?
|
||||||
|
|
||||||
|
model.errors.messages.map do |field, messages|
|
||||||
|
new(
|
||||||
|
full_messages: model.errors.full_messages_for(field),
|
||||||
|
field_name: field,
|
||||||
|
messages: messages,
|
||||||
|
path: ["attributes", field]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_active_record_model_errors(errors)
|
||||||
|
return if errors.blank?
|
||||||
|
|
||||||
|
errors.messages.map do |field, messages|
|
||||||
|
new(
|
||||||
|
full_messages: errors.full_messages_for(field),
|
||||||
|
field_name: field,
|
||||||
|
messages: messages,
|
||||||
|
path: ["attributes", field]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
22
app/graphql/types/buy_crypto_order_type.rb
Normal file
22
app/graphql/types/buy_crypto_order_type.rb
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
module Types
|
||||||
|
class BuyCryptoOrderType < Types::BaseObject
|
||||||
|
implements GraphQL::Types::Relay::Node
|
||||||
|
global_id_field :id
|
||||||
|
|
||||||
|
graphql_name "BuyCryptoOrder"
|
||||||
|
|
||||||
|
field :id, ID, null: false
|
||||||
|
|
||||||
|
field :currency, CurrencyType, null: false
|
||||||
|
def currency
|
||||||
|
dataloader.with(Dataloader::Source, Currency).load(object.currency_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
field :status, ProcessStatusEnum, null: false
|
||||||
|
field :paid_amount_cents, Integer, null: false
|
||||||
|
field :received_amount, String, null: true
|
||||||
|
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
|
||||||
|
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -9,7 +9,5 @@ module Types
|
|||||||
field :id, ID, null: false
|
field :id, ID, null: false
|
||||||
field :amount_cents, Integer, null: false
|
field :amount_cents, Integer, null: false
|
||||||
field :amount_currency, String, null: false
|
field :amount_currency, String, null: false
|
||||||
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
|
|
||||||
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
module Types
|
module Types
|
||||||
class MutationType < Types::BaseObject
|
class MutationType < Types::BaseObject
|
||||||
|
field :create_sell_crypto_order, mutation: Mutations::CreateSellCryptoOrder
|
||||||
|
field :create_buy_crypto_order, mutation: Mutations::CreateBuyCryptoOrder
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
10
app/graphql/types/process_status_enum.rb
Normal file
10
app/graphql/types/process_status_enum.rb
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
module Types
|
||||||
|
class ProcessStatusEnum < Types::BaseEnum
|
||||||
|
graphql_name "ProcessStatus"
|
||||||
|
|
||||||
|
value "PROCESSING", value: "processing"
|
||||||
|
value "COMPLETED", value: "completed"
|
||||||
|
value "CANCELED", value: "canceled"
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -18,5 +18,15 @@ module Types
|
|||||||
def fiat_balances
|
def fiat_balances
|
||||||
Pundit.policy_scope(current_user, FiatBalance)
|
Pundit.policy_scope(current_user, FiatBalance)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
field :sell_crypto_orders, SellCryptoOrderType.connection_type, null: false
|
||||||
|
def sell_crypto_orders
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
field :buy_crypto_orders, BuyCryptoOrderType.connection_type, null: false
|
||||||
|
def buy_crypto_orders
|
||||||
|
[]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
11
app/graphql/types/record_invalid_type.rb
Normal file
11
app/graphql/types/record_invalid_type.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Types
|
||||||
|
class RecordInvalidType < Types::BaseObject
|
||||||
|
graphql_name "RecordInvalid"
|
||||||
|
field :full_messages, [String], null: false
|
||||||
|
field :field_name, String, null: true
|
||||||
|
field :messages, [String], null: true
|
||||||
|
field :path, [String], null: true
|
||||||
|
end
|
||||||
|
end
|
||||||
22
app/graphql/types/sell_crypto_order_type.rb
Normal file
22
app/graphql/types/sell_crypto_order_type.rb
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
module Types
|
||||||
|
class SellCryptoOrderType < Types::BaseObject
|
||||||
|
implements GraphQL::Types::Relay::Node
|
||||||
|
global_id_field :id
|
||||||
|
|
||||||
|
graphql_name "SellCryptoOrder"
|
||||||
|
|
||||||
|
field :id, ID, null: false
|
||||||
|
|
||||||
|
field :currency, CurrencyType, null: false
|
||||||
|
def currency
|
||||||
|
dataloader.with(Dataloader::Source, Currency).load(object.currency_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
field :status, ProcessStatusEnum, null: false
|
||||||
|
field :paid_amount, String, null: false
|
||||||
|
field :received_amount_cents, Integer, null: true
|
||||||
|
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
|
||||||
|
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class XStakeSchema < GraphQL::Schema
|
class XStakeSchema < GraphQL::Schema
|
||||||
# mutation(Types::MutationType)
|
mutation(Types::MutationType)
|
||||||
query(Types::QueryType)
|
query(Types::QueryType)
|
||||||
use GraphQL::Dataloader
|
use GraphQL::Dataloader
|
||||||
|
|
||||||
|
|||||||
180
app/javascript/__generated__/schema.graphql
generated
180
app/javascript/__generated__/schema.graphql
generated
@@ -34,19 +34,24 @@ type BalanceEdge {
|
|||||||
node: Balance!
|
node: Balance!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Currency implements Node {
|
type BuyCryptoOrder implements Node {
|
||||||
|
createdAt: ISO8601DateTime!
|
||||||
|
currency: Currency!
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
paidAmountCents: Int!
|
||||||
|
receivedAmount: Float
|
||||||
|
status: ProcessStatus!
|
||||||
|
updatedAt: ISO8601DateTime!
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The connection type for Currency.
|
The connection type for BuyCryptoOrder.
|
||||||
"""
|
"""
|
||||||
type CurrencyConnection {
|
type BuyCryptoOrderConnection {
|
||||||
"""
|
"""
|
||||||
A list of edges.
|
A list of edges.
|
||||||
"""
|
"""
|
||||||
edges: [CurrencyEdge!]!
|
edges: [BuyCryptoOrderEdge!]!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Information to aid in pagination.
|
Information to aid in pagination.
|
||||||
@@ -57,7 +62,7 @@ type CurrencyConnection {
|
|||||||
"""
|
"""
|
||||||
An edge in a connection.
|
An edge in a connection.
|
||||||
"""
|
"""
|
||||||
type CurrencyEdge {
|
type BuyCryptoOrderEdge {
|
||||||
"""
|
"""
|
||||||
A cursor for use in pagination.
|
A cursor for use in pagination.
|
||||||
"""
|
"""
|
||||||
@@ -66,7 +71,82 @@ type CurrencyEdge {
|
|||||||
"""
|
"""
|
||||||
The item at the end of the edge.
|
The item at the end of the edge.
|
||||||
"""
|
"""
|
||||||
node: Currency!
|
node: BuyCryptoOrder!
|
||||||
|
}
|
||||||
|
|
||||||
|
input CreateBuyCryptoOrderAttributesInput {
|
||||||
|
"""
|
||||||
|
Amount to be paid
|
||||||
|
"""
|
||||||
|
amount: Float!
|
||||||
|
currencyId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Autogenerated input type of CreateBuyCryptoOrder
|
||||||
|
"""
|
||||||
|
input CreateBuyCryptoOrderInput {
|
||||||
|
"""
|
||||||
|
A unique identifier for the client performing the mutation.
|
||||||
|
"""
|
||||||
|
clientMutationId: String
|
||||||
|
order: CreateBuyCryptoOrderAttributesInput!
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Autogenerated return type of CreateBuyCryptoOrder
|
||||||
|
"""
|
||||||
|
type CreateBuyCryptoOrderPayload {
|
||||||
|
"""
|
||||||
|
A unique identifier for the client performing the mutation.
|
||||||
|
"""
|
||||||
|
clientMutationId: String
|
||||||
|
|
||||||
|
"""
|
||||||
|
Errors encountered during execution of the mutation.
|
||||||
|
"""
|
||||||
|
errors: [String!]
|
||||||
|
order: BuyCryptoOrder!
|
||||||
|
}
|
||||||
|
|
||||||
|
input CreateSellCryptoOrderAttributesInput {
|
||||||
|
"""
|
||||||
|
Amount to be paid
|
||||||
|
"""
|
||||||
|
amount: Float!
|
||||||
|
currencyId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Autogenerated input type of CreateSellCryptoOrder
|
||||||
|
"""
|
||||||
|
input CreateSellCryptoOrderInput {
|
||||||
|
"""
|
||||||
|
A unique identifier for the client performing the mutation.
|
||||||
|
"""
|
||||||
|
clientMutationId: String
|
||||||
|
order: CreateSellCryptoOrderAttributesInput!
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Autogenerated return type of CreateSellCryptoOrder
|
||||||
|
"""
|
||||||
|
type CreateSellCryptoOrderPayload {
|
||||||
|
"""
|
||||||
|
A unique identifier for the client performing the mutation.
|
||||||
|
"""
|
||||||
|
clientMutationId: String
|
||||||
|
|
||||||
|
"""
|
||||||
|
Errors encountered during execution of the mutation.
|
||||||
|
"""
|
||||||
|
errors: [String!]
|
||||||
|
order: SellCryptoOrder!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Currency implements Node {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
type FiatBalance implements Node {
|
type FiatBalance implements Node {
|
||||||
@@ -112,6 +192,21 @@ An ISO 8601-encoded datetime
|
|||||||
"""
|
"""
|
||||||
scalar ISO8601DateTime
|
scalar ISO8601DateTime
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
createBuyCryptoOrder(
|
||||||
|
"""
|
||||||
|
Parameters for CreateBuyCryptoOrder
|
||||||
|
"""
|
||||||
|
input: CreateBuyCryptoOrderInput!
|
||||||
|
): CreateBuyCryptoOrderPayload
|
||||||
|
createSellCryptoOrder(
|
||||||
|
"""
|
||||||
|
Parameters for CreateSellCryptoOrder
|
||||||
|
"""
|
||||||
|
input: CreateSellCryptoOrderInput!
|
||||||
|
): CreateSellCryptoOrderPayload
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
An object with an ID.
|
An object with an ID.
|
||||||
"""
|
"""
|
||||||
@@ -147,6 +242,12 @@ type PageInfo {
|
|||||||
startCursor: String
|
startCursor: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ProcessStatus {
|
||||||
|
CANCELED
|
||||||
|
COMPLETED
|
||||||
|
PROCESSING
|
||||||
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
balances(
|
balances(
|
||||||
"""
|
"""
|
||||||
@@ -169,7 +270,7 @@ type Query {
|
|||||||
"""
|
"""
|
||||||
last: Int
|
last: Int
|
||||||
): BalanceConnection!
|
): BalanceConnection!
|
||||||
currencies(
|
buyCryptoOrders(
|
||||||
"""
|
"""
|
||||||
Returns the elements in the list that come after the specified cursor.
|
Returns the elements in the list that come after the specified cursor.
|
||||||
"""
|
"""
|
||||||
@@ -189,7 +290,7 @@ type Query {
|
|||||||
Returns the last _n_ elements from the list.
|
Returns the last _n_ elements from the list.
|
||||||
"""
|
"""
|
||||||
last: Int
|
last: Int
|
||||||
): CurrencyConnection!
|
): BuyCryptoOrderConnection!
|
||||||
currentUser: User
|
currentUser: User
|
||||||
fiatBalances(
|
fiatBalances(
|
||||||
"""
|
"""
|
||||||
@@ -232,6 +333,67 @@ type Query {
|
|||||||
"""
|
"""
|
||||||
ids: [ID!]!
|
ids: [ID!]!
|
||||||
): [Node]!
|
): [Node]!
|
||||||
|
sellCryptoOrders(
|
||||||
|
"""
|
||||||
|
Returns the elements in the list that come after the specified cursor.
|
||||||
|
"""
|
||||||
|
after: String
|
||||||
|
|
||||||
|
"""
|
||||||
|
Returns the elements in the list that come before the specified cursor.
|
||||||
|
"""
|
||||||
|
before: String
|
||||||
|
|
||||||
|
"""
|
||||||
|
Returns the first _n_ elements from the list.
|
||||||
|
"""
|
||||||
|
first: Int
|
||||||
|
|
||||||
|
"""
|
||||||
|
Returns the last _n_ elements from the list.
|
||||||
|
"""
|
||||||
|
last: Int
|
||||||
|
): SellCryptoOrderConnection!
|
||||||
|
}
|
||||||
|
|
||||||
|
type SellCryptoOrder implements Node {
|
||||||
|
createdAt: ISO8601DateTime!
|
||||||
|
currency: Currency!
|
||||||
|
id: ID!
|
||||||
|
paidAmount: Float!
|
||||||
|
receivedAmountCents: Int
|
||||||
|
status: ProcessStatus!
|
||||||
|
updatedAt: ISO8601DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
The connection type for SellCryptoOrder.
|
||||||
|
"""
|
||||||
|
type SellCryptoOrderConnection {
|
||||||
|
"""
|
||||||
|
A list of edges.
|
||||||
|
"""
|
||||||
|
edges: [SellCryptoOrderEdge!]!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Information to aid in pagination.
|
||||||
|
"""
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
An edge in a connection.
|
||||||
|
"""
|
||||||
|
type SellCryptoOrderEdge {
|
||||||
|
"""
|
||||||
|
A cursor for use in pagination.
|
||||||
|
"""
|
||||||
|
cursor: String!
|
||||||
|
|
||||||
|
"""
|
||||||
|
The item at the end of the edge.
|
||||||
|
"""
|
||||||
|
node: SellCryptoOrder!
|
||||||
}
|
}
|
||||||
|
|
||||||
type User {
|
type User {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class User < ApplicationRecord
|
|||||||
:recoverable, :rememberable, :validatable
|
:recoverable, :rememberable, :validatable
|
||||||
|
|
||||||
has_many :documents, class_name: "UserDocument", dependent: :destroy
|
has_many :documents, class_name: "UserDocument", dependent: :destroy
|
||||||
has_one :balance, dependent: :restrict_with_error
|
has_many :balances, dependent: :restrict_with_error
|
||||||
has_one :fiat_balance, dependent: :restrict_with_error
|
has_one :fiat_balance, dependent: :restrict_with_error
|
||||||
|
|
||||||
validates :first_name, :last_name, :email, presence: true
|
validates :first_name, :last_name, :email, presence: true
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
describe Inputs::CreateBuyCryptoOrderAttributesInput do
|
||||||
|
subject { described_class }
|
||||||
|
|
||||||
|
describe "fields" do
|
||||||
|
it { is_expected.to(accept_argument("currency_id").of_type("ID!")) }
|
||||||
|
it { is_expected.to(accept_argument("amount_cents").of_type("Int!")) }
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
describe Inputs::CreateSellCryptoOrderAttributesInput do
|
||||||
|
subject { described_class }
|
||||||
|
|
||||||
|
describe "fields" do
|
||||||
|
it { is_expected.to(accept_argument("currency_id").of_type("ID!")) }
|
||||||
|
it { is_expected.to(accept_argument("amount").of_type("String!")) }
|
||||||
|
end
|
||||||
|
end
|
||||||
121
spec/graphql/mutations/create_buy_crypto_order_spec.rb
Normal file
121
spec/graphql/mutations/create_buy_crypto_order_spec.rb
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe(Mutations::CreateBuyCryptoOrder, type: :mutation) do
|
||||||
|
let(:query_string) do
|
||||||
|
<<~GQL
|
||||||
|
mutation($currencyId: ID!, $amountCents: Int!) {
|
||||||
|
createBuyCryptoOrder(input: {
|
||||||
|
order: {
|
||||||
|
currencyId: $currencyId,
|
||||||
|
amountCents: $amountCents,
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
errors {
|
||||||
|
fullMessages
|
||||||
|
fieldName
|
||||||
|
messages
|
||||||
|
path
|
||||||
|
}
|
||||||
|
order {
|
||||||
|
status
|
||||||
|
paidAmountCents
|
||||||
|
receivedAmount
|
||||||
|
currency {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GQL
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the user has enough balance" do
|
||||||
|
it "withdraws from his account and creates a buy order" do
|
||||||
|
currency = create(:currency)
|
||||||
|
user = create(
|
||||||
|
:user,
|
||||||
|
fiat_balance: build(
|
||||||
|
:fiat_balance,
|
||||||
|
amount_cents: 100_00
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
currency_global_id = GraphQL::Schema::UniqueWithinType.encode("Currency", currency.id)
|
||||||
|
|
||||||
|
variables = {
|
||||||
|
"currencyId": currency_global_id,
|
||||||
|
"amountCents": 90_00,
|
||||||
|
}
|
||||||
|
|
||||||
|
context = { current_user: user }
|
||||||
|
|
||||||
|
result = XStakeSchema.execute(
|
||||||
|
query_string,
|
||||||
|
variables: variables,
|
||||||
|
context: context
|
||||||
|
).to_h.with_indifferent_access
|
||||||
|
|
||||||
|
expect(result).to(eq({
|
||||||
|
"data" => {
|
||||||
|
"createBuyCryptoOrder" => {
|
||||||
|
"errors" => nil,
|
||||||
|
"order" => {
|
||||||
|
"status" => "PROCESSING",
|
||||||
|
"paidAmountCents" => 90_00,
|
||||||
|
"receivedAmount" => nil,
|
||||||
|
"currency" => {
|
||||||
|
"name" => "CAKE",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
expect(user.fiat_balance.reload.amount_cents).to(eq(10_00))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the user does not have enough balance" do
|
||||||
|
it "returns withdrawl error" do
|
||||||
|
currency = create(:currency)
|
||||||
|
user = create(
|
||||||
|
:user,
|
||||||
|
fiat_balance: build(
|
||||||
|
:fiat_balance,
|
||||||
|
amount_cents: 80_00
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
currency_global_id = GraphQL::Schema::UniqueWithinType.encode("Currency", currency.id)
|
||||||
|
|
||||||
|
variables = {
|
||||||
|
"currencyId": currency_global_id,
|
||||||
|
"amountCents": 90_00,
|
||||||
|
}
|
||||||
|
|
||||||
|
context = { current_user: user }
|
||||||
|
|
||||||
|
result = XStakeSchema.execute(
|
||||||
|
query_string,
|
||||||
|
variables: variables,
|
||||||
|
context: context
|
||||||
|
).to_h.with_indifferent_access
|
||||||
|
|
||||||
|
expect(result).to(eq({
|
||||||
|
"data" => {
|
||||||
|
"createBuyCryptoOrder" => {
|
||||||
|
"errors" => [{
|
||||||
|
"fullMessages" => ["Quantia saldo insuficiente"],
|
||||||
|
"fieldName" => "amount_cents",
|
||||||
|
"messages" => ["saldo insuficiente"],
|
||||||
|
"path" => ["attributes", "amount_cents"],
|
||||||
|
}],
|
||||||
|
"order" => nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
121
spec/graphql/mutations/create_sell_crypto_order_spec.rb
Normal file
121
spec/graphql/mutations/create_sell_crypto_order_spec.rb
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe(Mutations::CreateSellCryptoOrder, type: :mutation) do
|
||||||
|
let(:query_string) do
|
||||||
|
<<~GQL
|
||||||
|
mutation($currencyId: ID!, $amount: String!) {
|
||||||
|
createSellCryptoOrder(input: {
|
||||||
|
order: {
|
||||||
|
currencyId: $currencyId,
|
||||||
|
amount: $amount,
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
errors {
|
||||||
|
fullMessages
|
||||||
|
fieldName
|
||||||
|
messages
|
||||||
|
path
|
||||||
|
}
|
||||||
|
order {
|
||||||
|
status
|
||||||
|
paidAmount
|
||||||
|
receivedAmountCents
|
||||||
|
currency {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GQL
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the user has enough balance" do
|
||||||
|
it "withdraws from his account and creates a buy order" do
|
||||||
|
currency = create(:currency)
|
||||||
|
user = create(
|
||||||
|
:user,
|
||||||
|
balances: [
|
||||||
|
build(:balance, currency: currency, amount: 1.0034),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
currency_global_id = GraphQL::Schema::UniqueWithinType.encode("Currency", currency.id)
|
||||||
|
|
||||||
|
variables = {
|
||||||
|
"currencyId": currency_global_id,
|
||||||
|
"amount": "0.80",
|
||||||
|
}
|
||||||
|
|
||||||
|
context = { current_user: user }
|
||||||
|
|
||||||
|
result = XStakeSchema.execute(
|
||||||
|
query_string,
|
||||||
|
variables: variables,
|
||||||
|
context: context
|
||||||
|
).to_h.with_indifferent_access
|
||||||
|
|
||||||
|
expect(result).to(eq({
|
||||||
|
"data" => {
|
||||||
|
"createSellCryptoOrder" => {
|
||||||
|
"errors" => nil,
|
||||||
|
"order" => {
|
||||||
|
"status" => "PROCESSING",
|
||||||
|
"paidAmount" => "0.8",
|
||||||
|
"receivedAmountCents" => nil,
|
||||||
|
"currency" => {
|
||||||
|
"name" => "CAKE",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
expect(user.balances.first.reload.amount.to_s).to(eq("0.2034"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the user does not have enough balance" do
|
||||||
|
it "returns withdrawl error" do
|
||||||
|
currency = create(:currency)
|
||||||
|
user = create(
|
||||||
|
:user,
|
||||||
|
balances: [
|
||||||
|
build(:balance, currency: currency, amount: 0.0034),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
currency_global_id = GraphQL::Schema::UniqueWithinType.encode("Currency", currency.id)
|
||||||
|
|
||||||
|
variables = {
|
||||||
|
"currencyId": currency_global_id,
|
||||||
|
"amount": "0.80",
|
||||||
|
}
|
||||||
|
|
||||||
|
context = { current_user: user }
|
||||||
|
|
||||||
|
result = XStakeSchema.execute(
|
||||||
|
query_string,
|
||||||
|
variables: variables,
|
||||||
|
context: context
|
||||||
|
).to_h.with_indifferent_access
|
||||||
|
|
||||||
|
expect(result).to(eq({
|
||||||
|
"data" => {
|
||||||
|
"createSellCryptoOrder" => {
|
||||||
|
"errors" => [{
|
||||||
|
"fullMessages" => ["Quantia saldo insuficiente"],
|
||||||
|
"fieldName" => "amount",
|
||||||
|
"messages" => ["saldo insuficiente"],
|
||||||
|
"path" => ["attributes", "amount"],
|
||||||
|
}],
|
||||||
|
"order" => nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
expect(user.balances.first.reload.amount.to_s).to(eq("0.0034"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
16
spec/graphql/types/buy_crypto_order_type_spec.rb
Normal file
16
spec/graphql/types/buy_crypto_order_type_spec.rb
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe(Types::BuyCryptoOrderType) do
|
||||||
|
subject { described_class }
|
||||||
|
|
||||||
|
describe "arguments" do
|
||||||
|
it { is_expected.to(have_a_field(:id).of_type("ID!")) }
|
||||||
|
it { is_expected.to(have_a_field(:currency).of_type("Currency!")) }
|
||||||
|
it { is_expected.to(have_a_field(:paid_amount_cents).of_type("Int!")) }
|
||||||
|
it { is_expected.to(have_a_field(:received_amount).of_type("String")) }
|
||||||
|
it { is_expected.to(have_a_field(:status).of_type("ProcessStatus!")) }
|
||||||
|
it { is_expected.to(have_a_field(:created_at).of_type("ISO8601DateTime!")) }
|
||||||
|
it { is_expected.to(have_a_field(:updated_at).of_type("ISO8601DateTime!")) }
|
||||||
|
end
|
||||||
|
end
|
||||||
9
spec/graphql/types/process_status_enum_spec.rb
Normal file
9
spec/graphql/types/process_status_enum_spec.rb
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
describe Types::ProcessStatusEnum do
|
||||||
|
describe "values" do
|
||||||
|
it { expect(described_class.values.keys).to(match(["PROCESSING", "COMPLETED", "CANCELED"])) }
|
||||||
|
end
|
||||||
|
end
|
||||||
14
spec/graphql/types/record_invalid_type_spec.rb
Normal file
14
spec/graphql/types/record_invalid_type_spec.rb
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
describe Types::RecordInvalidType do
|
||||||
|
subject { described_class }
|
||||||
|
|
||||||
|
describe "fields" do
|
||||||
|
it { is_expected.to(have_field(:full_messages).of_type("[String!]!")) }
|
||||||
|
it { is_expected.to(have_field(:field_name).of_type("String")) }
|
||||||
|
it { is_expected.to(have_field(:messages).of_type("[String!]")) }
|
||||||
|
it { is_expected.to(have_field(:path).of_type("[String!]")) }
|
||||||
|
end
|
||||||
|
end
|
||||||
16
spec/graphql/types/sell_crypto_order_type_spec.rb
Normal file
16
spec/graphql/types/sell_crypto_order_type_spec.rb
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe(Types::SellCryptoOrderType) do
|
||||||
|
subject { described_class }
|
||||||
|
|
||||||
|
describe "arguments" do
|
||||||
|
it { is_expected.to(have_a_field(:id).of_type("ID!")) }
|
||||||
|
it { is_expected.to(have_a_field(:currency).of_type("Currency!")) }
|
||||||
|
it { is_expected.to(have_a_field(:paid_amount).of_type("String!")) }
|
||||||
|
it { is_expected.to(have_a_field(:received_amount_cents).of_type("Int")) }
|
||||||
|
it { is_expected.to(have_a_field(:status).of_type("ProcessStatus!")) }
|
||||||
|
it { is_expected.to(have_a_field(:created_at).of_type("ISO8601DateTime!")) }
|
||||||
|
it { is_expected.to(have_a_field(:updated_at).of_type("ISO8601DateTime!")) }
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -31,7 +31,7 @@ RSpec.describe(User, type: :model) do
|
|||||||
|
|
||||||
describe "associations" do
|
describe "associations" do
|
||||||
it { is_expected.to(have_many(:documents)) }
|
it { is_expected.to(have_many(:documents)) }
|
||||||
it { is_expected.to(have_one(:balance)) }
|
it { is_expected.to(have_many(:balances)) }
|
||||||
it { is_expected.to(have_one(:fiat_balance)) }
|
it { is_expected.to(have_one(:fiat_balance)) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user