From c16e705a4cabcd01731b77b38623491e16a618a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20K=C3=A4chele?= Date: Sat, 28 Oct 2023 14:06:21 +0200 Subject: [PATCH] Finish limit implementation --- lib/rex/limit.rb | 40 ++++++ spec/factories/limits.rb | 9 ++ spec/limit_spec.rb | 277 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 326 insertions(+) create mode 100644 spec/factories/limits.rb create mode 100644 spec/limit_spec.rb diff --git a/lib/rex/limit.rb b/lib/rex/limit.rb index 98007eb..e34bbd1 100644 --- a/lib/rex/limit.rb +++ b/lib/rex/limit.rb @@ -1,5 +1,10 @@ module Rex class Limit + attr_reader( + :first_order, + :last_order, + :price + ) def initialize(price) @price = price @first_order = nil @@ -11,13 +16,24 @@ module Rex @first_order end + def count + @order_count + end + def pop_first_order + return nil if empty? + order = @first_order @first_order = @first_order.next_order + @first_order&.previous_order = nil + if @first_order.nil? @last_order = nil end + order.next_order = nil + order.previous_order = nil + @order_count -= 1 order end @@ -27,11 +43,35 @@ module Rex @first_order = order else @last_order.next_order = order + order.previous_order = @last_order end @last_order = order @order_count += 1 end + # Assumption when calling: the order is part of the limit + def remove_order(order) + if @first_order == order + @first_order = order.next_order + end + + if @last_order == order + @last_order = order.previous_order + end + + previous_order = order.previous_order + next_order = order.next_order + + previous_order&.next_order = next_order + next_order&.previous_order = previous_order + + order.previous_order = nil + order.next_order = nil + @order_count -= 1 + + order + end + def empty? @order_count == 0 end diff --git a/spec/factories/limits.rb b/spec/factories/limits.rb new file mode 100644 index 0000000..75d8c73 --- /dev/null +++ b/spec/factories/limits.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :limit, class: Rex::Limit do + transient do + price { 100 } + end + end +end diff --git a/spec/limit_spec.rb b/spec/limit_spec.rb new file mode 100644 index 0000000..b96aadc --- /dev/null +++ b/spec/limit_spec.rb @@ -0,0 +1,277 @@ +# frozen_string_literal: true + +RSpec.describe Rex::Limit do + let(:instance) { described_class.new(100) } + let(:order_a) { build(:order) } + let(:order_b) { build(:order) } + let(:order_c) { build(:order) } + + describe "#add_order" do + context "when limit has no orders yet" do + it "increases the count by one" do + expect do + instance.add_order(order_a) + end.to change(instance, :count).by(1) + end + + it "sets the first and last order correctly" do + instance.add_order(order_a) + + expect(instance.first_order).to eq(order_a) + expect(instance.last_order).to eq(order_a) + end + + it "does not change the previous/next order values" do + instance.add_order(order_a) + + expect(order_a.previous_order).to eq(nil) + expect(order_a.next_order).to eq(nil) + end + end + + context "when limit orders already in it" do + before do + instance.add_order(order_a) + end + + it "increases the count by one" do + expect do + instance.add_order(order_b) + end.to change(instance, :count).by(1) + end + + it "sets the first and last order correctly" do + instance.add_order(order_b) + + expect(instance.first_order).to eq(order_a) + expect(instance.last_order).to eq(order_b) + end + + it "does not change the previous/next order values" do + instance.add_order(order_b) + + expect(order_a.previous_order).to eq(nil) + expect(order_a.next_order).to eq(order_b) + + expect(order_b.previous_order).to eq(order_a) + expect(order_b.next_order).to eq(nil) + end + end + end + + describe "#peek_first_order" do + subject { instance.peek_first_order } + + context "when limit is empty" do + it { is_expected.to eq(nil) } + end + + context "when limit has one item" do + before do + instance.add_order(order_a) + end + + it "returns the first first order" do + expect(subject).to eq(order_a) + end + end + + context "when limit has two items" do + before do + instance.add_order(order_a) + instance.add_order(order_b) + end + + it "returns the first order" do + expect(subject).to eq(order_a) + end + end + end + + describe "#pop_first_order" do + context "when limit is empty" do + it "returns nil" do + expect(instance.pop_first_order).to eq(nil) + end + + it "does not change the count" do + expect { instance.pop_first_order }.not_to change(instance, :count) + end + end + + context "when limit has two orders" do + before do + instance.add_order(order_a) + instance.add_order(order_b) + end + + it "decrements the count by 1" do + expect { instance.pop_first_order }.to change(instance, :count).by(-1) + end + + it "returns the first order" do + expect(instance.pop_first_order).to eq(order_a) + end + + it "adjusts the first order" do + expect { instance.pop_first_order }.to change(instance, :first_order).from(order_a).to(order_b) + end + + it "sets the previous/next order accordingly" do + popped_order = instance.pop_first_order + + expect(popped_order.next_order).to eq(nil) + expect(popped_order.previous_order).to eq(nil) + end + + it "updates the previous/next order links of the new first order" do + instance.pop_first_order + new_first_order = instance.peek_first_order + + expect(new_first_order.previous_order).to eq(nil) + expect(new_first_order.next_order).to eq(nil) + end + end + + context "when limit has only one order" do + before do + instance.add_order(order_a) + end + + it "decrements the count by 1" do + expect { instance.pop_first_order }.to change(instance, :count).to(0) + end + + it "returns the first order" do + expect(instance.pop_first_order).to eq(order_a) + end + + it "adjusts the first order" do + expect { instance.pop_first_order }.to change(instance, :first_order).from(order_a).to(nil) + end + + it "sets the previous/next of the returned order to nil" do + popped_order = instance.pop_first_order + + expect(popped_order.next_order).to eq(nil) + expect(popped_order.previous_order).to eq(nil) + end + + it "sets the first/last order correctly" do + instance.pop_first_order + + expect(instance.first_order).to eq(nil) + expect(instance.last_order).to eq(nil) + end + end + end + + describe "#remove_order" do + context "when the order is the first order" do + before do + instance.add_order(order_a) + end + + it "decrements the count by 1" do + expect { instance.remove_order(order_a) }.to change(instance, :count).by(-1) + end + + it "returns the removed order" do + expect(instance.remove_order(order_a)).to eq(order_a) + end + + it "adjusts the previous/next order of the removed order" do + instance.remove_order(order_a) + + expect(order_a.previous_order).to eq(nil) + expect(order_a.next_order).to eq(nil) + end + + it "adjusts the first/last order in the limit" do + instance.remove_order(order_a) + + expect(instance.first_order).to eq(nil) + expect(instance.last_order).to eq(nil) + end + end + + context "when the order is the last order" do + before do + instance.add_order(order_a) + instance.add_order(order_b) + end + + it "decrements the count by 1" do + expect { instance.remove_order(order_b) }.to change(instance, :count).by(-1) + end + + it "returns the removed order" do + expect(instance.remove_order(order_b)).to eq(order_b) + end + + it "adjusts the previous/next order of the removed order" do + instance.remove_order(order_b) + + expect(order_b.previous_order).to eq(nil) + expect(order_b.next_order).to eq(nil) + end + + it "adjusts the first/last order in the limit" do + instance.remove_order(order_b) + + expect(instance.first_order).to eq(order_a) + expect(instance.last_order).to eq(order_a) + end + end + + context "when the order is the in the middle of the queue" do + before do + instance.add_order(order_a) + instance.add_order(order_b) + instance.add_order(order_c) + end + + it "decrements the count by 1" do + expect { instance.remove_order(order_b) }.to change(instance, :count).by(-1) + end + + it "returns the removed order" do + expect(instance.remove_order(order_b)).to eq(order_b) + end + + it "adjusts the previous/next order of the removed order" do + instance.remove_order(order_b) + + expect(order_b.previous_order).to eq(nil) + expect(order_b.next_order).to eq(nil) + end + + it "does not adjust the first/last order in the limit" do + instance.remove_order(order_b) + + expect(instance.first_order).to eq(order_a) + expect(instance.last_order).to eq(order_c) + end + + it "adjusts the previous/next order of the remaining orders" do + instance.remove_order(order_b) + + expect(order_a.previous_order).to eq(nil) + expect(order_a.next_order).to eq(order_c) + + expect(order_c.previous_order).to eq(order_a) + expect(order_c.next_order).to eq(nil) + end + + it "ends up in a valid state after deleting all the orders" do + [order_a, order_b, order_c].shuffle.each do |order| + instance.remove_order(order) + end + + expect(instance.first_order).to eq(nil) + expect(instance.last_order).to eq(nil) + expect(instance.count).to eq(0) + end + end + end +end