Wednesday, July 15, 2026 3:56:36 AM
> server_spec.rb
# frozen_string_literal: true

RSpec.describe ESM::Server do
  let!(:community) { create(:community) }

  describe ".with_server_id" do
    let!(:server) { create(:server, community: community, server_id: "test_malden") }

    it "matches exact server_id" do
      expect(described_class.with_server_id("test_malden")).to include(server)
    end

    it "is case insensitive" do
      expect(described_class.with_server_id("TEST_MALDEN")).to include(server)
      expect(described_class.with_server_id("Test_Malden")).to include(server)
      expect(described_class.with_server_id("tEsT_mAlDeN")).to include(server)
    end

    it "does not match partial server_id" do
      expect(described_class.with_server_id("test")).not_to include(server)
      expect(described_class.with_server_id("malden")).not_to include(server)
    end
  end

  describe ".by_server_id_fuzzy" do
    let!(:server) { create(:server, community: community, server_id: "esm_tanoa") }

    it "matches partial server_id at the beginning" do
      expect(described_class.by_server_id_fuzzy("esm")).to include(server)
    end

    it "matches partial server_id at the end" do
      expect(described_class.by_server_id_fuzzy("tanoa")).to include(server)
    end

    it "matches partial server_id in the middle" do
      expect(described_class.by_server_id_fuzzy("m_tan")).to include(server)
    end

    it "is case insensitive" do
      expect(described_class.by_server_id_fuzzy("ESM")).to include(server)
      expect(described_class.by_server_id_fuzzy("TANOA")).to include(server)
    end

    it "does not match unrelated strings" do
      expect(described_class.by_server_id_fuzzy("xyz")).not_to include(server)
    end
  end

  describe ".with_local_id" do
    let!(:server_t) { create(:server, community: community, server_id: "esm_t") }
    let!(:server_test) { create(:server, community: community, server_id: "esm_test") }
    let!(:server_testing) { create(:server, community: community, server_id: "esm_testing") }

    it "matches exact local_id" do
      result = described_class.with_local_id("t")
      expect(result).to include(server_t)
      expect(result).not_to include(server_test)
      expect(result).not_to include(server_testing)
    end

    it "does not match partial local_id (the original bug)" do
      # This was the bug: searching for "t" would match "test" because
      # the underscore was being treated as a SQL wildcard
      result = described_class.with_local_id("t")
      expect(result.count).to eq(1)
      expect(result.first).to eq(server_t)
    end

    it "matches longer local_ids exactly" do
      result = described_class.with_local_id("test")
      expect(result).to include(server_test)
      expect(result).not_to include(server_t)
      expect(result).not_to include(server_testing)
    end

    it "is case insensitive" do
      expect(described_class.with_local_id("T")).to include(server_t)
      expect(described_class.with_local_id("TEST")).to include(server_test)
      expect(described_class.with_local_id("TeSt")).to include(server_test)
    end

    it "matches any community prefix with the same local_id" do
      other_community = create(:community)
      other_server = create(:server, community: other_community, server_id: "#{other_community.community_id}_test")

      result = described_class.with_local_id("test")
      expect(result).to include(server_test)
      expect(result).to include(other_server)
    end

    it "escapes SQL wildcards in the local_id input" do
      # Create a server with a local_id containing SQL wildcard characters
      server_with_percent = create(:server, community: community, server_id: "esm_100%done")

      # Searching for "100%" should only match the exact local_id
      result = described_class.with_local_id("100%done")
      expect(result).to include(server_with_percent)

      # Searching for "100" should NOT match "100%done" (% shouldn't act as wildcard)
      result = described_class.with_local_id("100")
      expect(result).not_to include(server_with_percent)
    end

    it "escapes underscore wildcards in the local_id input" do
      # Create servers where underscore matters
      server_a_b = create(:server, community: community, server_id: "esm_a_b")
      server_axb = create(:server, community: community, server_id: "esm_aXb")

      # Searching for "a_b" should match exactly, not treat _ as wildcard
      result = described_class.with_local_id("a_b")
      expect(result).to include(server_a_b)
      expect(result).not_to include(server_axb)
    end
  end

  describe ".find_by_server_id" do
    let!(:server) { create(:server, community: community, server_id: "abc_malden") }

    it "finds server by exact server_id" do
      expect(described_class.find_by_server_id("abc_malden")).to eq(server)
    end

    it "is case insensitive" do
      expect(described_class.find_by_server_id("ABC_MALDEN")).to eq(server)
      expect(described_class.find_by_server_id("Abc_Malden")).to eq(server)
    end

    it "returns nil when not found" do
      expect(described_class.find_by_server_id("nonexistent")).to be_nil
    end

    it "includes community association" do
      result = described_class.find_by_server_id("abc_malden")
      expect(result.association(:community)).to be_loaded
    end
  end

  describe "#local_id" do
    it "extracts the local_id from server_id" do
      server = create(:server, community: community, server_id: "esm_malden")
      expect(server.local_id).to eq("malden")
    end

    it "handles local_ids with underscores" do
      server = create(:server, community: community, server_id: "esm_my_server_name")
      expect(server.local_id).to eq("my_server_name")
    end

    it "handles single character local_ids" do
      server = create(:server, community: community, server_id: "esm_t")
      expect(server.local_id).to eq("t")
    end
  end

  describe "callbacks" do
    describe "before_validation :generate_public_id" do
      it "generates a UUID on create" do
        server = create(:server, community: community)

        expect(server.public_id).to be_present
        expect(server.public_id).to match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)
      end

      it "does not overwrite existing public_id" do
        existing_id = SecureRandom.uuid
        server = create(:server, community: community, public_id: existing_id)

        expect(server.public_id).to eq(existing_id)
      end
    end

    describe "before_validation :generate_key" do
      it "generates a 64-character server_key on create" do
        server = create(:server, community: community)

        expect(server.server_key).to be_present
        expect(server.server_key.length).to eq(64)
      end

      it "does not overwrite existing server_key" do
        existing_key = "x" * 64
        server = create(:server, community: community, server_key: existing_key)

        expect(server.server_key).to eq(existing_key)
      end
    end

    describe "after_create :create_server_setting" do
      it "creates a server_setting automatically" do
        server = create(:server, :with_callbacks, community: community)

        expect(server.server_setting).to be_present
        expect(server.server_setting).to be_a(ESM::ServerSetting)
      end
    end

    describe "after_create :create_default_reward" do
      it "creates a default reward automatically" do
        server = create(:server, :with_callbacks, community: community)

        expect(server.server_rewards.default).to be_present
      end
    end
  end

  describe "#token" do
    let(:server) { create(:server, community: community) }

    it "returns a hash with access and secret" do
      token = server.token

      expect(token).to be_a(Hash)
      expect(token[:access]).to eq(server.public_id)
      expect(token[:secret]).to eq(server.server_key)
    end

    it "memoizes the result" do
      first_call = server.token
      second_call = server.token

      expect(first_call.object_id).to eq(second_call.object_id)
    end
  end

  describe "#version" do
    let(:server) { create(:server, community: community) }

    it "returns a Semantic::Version object" do
      server.update_column(:server_version, "2.1.0")
      expect(server.version).to be_a(Semantic::Version)
    end

    it "defaults to 1.0.0 when server_version is nil" do
      server.update_column(:server_version, nil)
      expect(server.version.to_s).to eq("1.0.0")
    end
  end

  describe "#version?" do
    let(:server) { create(:server, community: community) }

    it "returns true when version meets requirement" do
      server.update_column(:server_version, "2.1.0")

      expect(server.version?("2.0.0")).to be(true)
      expect(server.version?("2.1.0")).to be(true)
      expect(server.version?("1.0.0")).to be(true)
    end

    it "returns false when version does not meet requirement" do
      server.update_column(:server_version, "1.5.0")

      expect(server.version?("2.0.0")).to be(false)
    end
  end

  describe "#v2?" do
    let(:server) { create(:server, community: community) }

    it "returns true for v2+ servers" do
      server.update_column(:server_version, "2.0.0")
      expect(server.v2?).to be(true)

      server.update_column(:server_version, "2.5.0")
      expect(server.v2?).to be(true)
    end

    it "returns false for v1 servers" do
      server.update_column(:server_version, "1.9.9")
      expect(server.v2?).to be(false)
    end
  end

  describe "#uptime" do
    let(:server) { create(:server, community: community) }

    it "returns 'Offline' when server_start_time is nil" do
      server.update_column(:server_start_time, nil)
      expect(server.uptime).to eq("Offline")
    end

    it "returns time since server started" do
      server.update_column(:server_start_time, 2.hours.ago)
      expect(server.uptime).to match(/hour/i)
    end
  end

  describe "#time_left_before_restart" do
    let(:server) { create(:server, :with_setting, community: community) }

    it "returns 'Offline' when server_start_time is nil" do
      server.update_column(:server_start_time, nil)
      expect(server.time_left_before_restart).to eq("Offline")
    end

    it "calculates time until restart" do
      server.update_column(:server_start_time, Time.current)
      server.server_setting.update!(server_restart_hour: 4, server_restart_min: 0)

      result = server.time_left_before_restart
      expect(result).to match(/hour/i)
    end
  end

  describe "#time_since_last_connection" do
    let(:server) { create(:server, community: community) }

    it "returns time since disconnected_at" do
      server.update_column(:disconnected_at, 1.hour.ago)
      expect(server.time_since_last_connection).to match(/hour/i)
    end
  end

  describe "validations" do
    it "requires public_id to be unique" do
      server1 = create(:server, community: community)
      server2 = build(:server, community: community, public_id: server1.public_id)

      expect(server2).not_to be_valid
      expect(server2.errors[:public_id]).to include("has already been taken")
    end
  end
end
All opinions represented herein are my own
- © 2024 - 2026 itsthedevman
- build 43aa0d7