diff options
Diffstat (limited to 'spec/dispatch/adapter/interface/usage_report_spec.rb')
| -rw-r--r-- | spec/dispatch/adapter/interface/usage_report_spec.rb | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/spec/dispatch/adapter/interface/usage_report_spec.rb b/spec/dispatch/adapter/interface/usage_report_spec.rb new file mode 100644 index 0000000..6cb3079 --- /dev/null +++ b/spec/dispatch/adapter/interface/usage_report_spec.rb @@ -0,0 +1,171 @@ +# frozen_string_literal: true + +RSpec.describe "UsageReport and friends" do + describe Dispatch::Adapter::UsageWindow do + it "constructs with required keywords only" do + w = described_class.new(id: "daily", label: "Daily") + expect(w.id).to eq("daily") + expect(w.label).to eq("Daily") + expect(w.duration_ms).to be_nil + expect(w.resets_at).to be_nil + end + + it "accepts optional duration_ms and resets_at" do + t = Time.now + w = described_class.new(id: "hourly", label: "Hourly", duration_ms: 3_600_000, resets_at: t) + expect(w.duration_ms).to eq(3_600_000) + expect(w.resets_at).to eq(t) + end + + it "round-trips through to_h" do + w = described_class.new(id: "daily", label: "Daily", duration_ms: 86_400_000) + h = w.to_h + expect(h[:id]).to eq("daily") + expect(h[:label]).to eq("Daily") + expect(h[:duration_ms]).to eq(86_400_000) + expect(h[:resets_at]).to be_nil + end + end + + describe Dispatch::Adapter::UsageAmount do + it "constructs with required unit keyword only" do + a = described_class.new(unit: :tokens) + expect(a.unit).to eq(:tokens) + expect(a.used).to be_nil + expect(a.limit).to be_nil + expect(a.remaining).to be_nil + expect(a.used_fraction).to be_nil + expect(a.remaining_fraction).to be_nil + end + + it "accepts all optional fields" do + a = described_class.new( + unit: :requests, + used: 250, + limit: 1000, + remaining: 750, + used_fraction: 0.25, + remaining_fraction: 0.75 + ) + expect(a.used).to eq(250) + expect(a.limit).to eq(1000) + expect(a.remaining).to eq(750) + expect(a.used_fraction).to eq(0.25) + expect(a.remaining_fraction).to eq(0.75) + end + + it "round-trips through to_h" do + a = described_class.new(unit: :usd, used: 1.5, limit: 10.0) + h = a.to_h + expect(h[:unit]).to eq(:usd) + expect(h[:used]).to eq(1.5) + expect(h[:limit]).to eq(10.0) + expect(h[:remaining]).to be_nil + end + + it "supports all documented unit symbols" do + %i[percent tokens requests usd minutes bytes unknown].each do |u| + expect { described_class.new(unit: u) }.not_to raise_error + end + end + end + + describe Dispatch::Adapter::UsageLimitEntry do + let(:amount) { Dispatch::Adapter::UsageAmount.new(unit: :tokens, used: 100, limit: 1000) } + + it "constructs with required keywords only" do + entry = described_class.new(id: "token_limit", label: "Token Limit", scope: "org", amount: amount) + expect(entry.id).to eq("token_limit") + expect(entry.label).to eq("Token Limit") + expect(entry.scope).to eq("org") + expect(entry.amount).to eq(amount) + expect(entry.window).to be_nil + expect(entry.status).to eq(:unknown) + expect(entry.notes).to eq([]) + end + + it "accepts optional window, status, and notes" do + window = Dispatch::Adapter::UsageWindow.new(id: "daily", label: "Daily") + entry = described_class.new( + id: "req_limit", + label: "Request Limit", + scope: "user", + amount: amount, + window: window, + status: :ok, + notes: ["All good"] + ) + expect(entry.window).to eq(window) + expect(entry.status).to eq(:ok) + expect(entry.notes).to eq(["All good"]) + end + + it "supports all documented status values" do + %i[ok warning exhausted unknown].each do |s| + entry = described_class.new(id: "x", label: "X", scope: "org", amount: amount, status: s) + expect(entry.status).to eq(s) + end + end + + it "round-trips through to_h" do + entry = described_class.new(id: "e1", label: "E1", scope: "workspace", amount: amount) + h = entry.to_h + expect(h[:id]).to eq("e1") + expect(h[:label]).to eq("E1") + expect(h[:scope]).to eq("workspace") + expect(h[:status]).to eq(:unknown) + expect(h[:notes]).to eq([]) + end + end + + describe Dispatch::Adapter::UsageReport do + let(:amount) { Dispatch::Adapter::UsageAmount.new(unit: :requests, used: 5, limit: 100) } + let(:entry) do + Dispatch::Adapter::UsageLimitEntry.new( + id: "req", label: "Requests", scope: "account", amount: amount + ) + end + + it "constructs with required keywords only" do + report = described_class.new(provider: "acme", limits: [entry]) + expect(report.provider).to eq("acme") + expect(report.limits).to eq([entry]) + expect(report.fetched_at).to be_a(Time) + expect(report.metadata).to eq({}) + expect(report.raw).to be_nil + end + + it "accepts all optional fields" do + t = Time.now + raw = { "foo" => "bar" } + report = described_class.new( + provider: "acme", + limits: [entry], + fetched_at: t, + metadata: { source: "api" }, + raw: raw + ) + expect(report.fetched_at).to eq(t) + expect(report.metadata).to eq({ source: "api" }) + expect(report.raw).to eq(raw) + end + + it "round-trips through to_h" do + t = Time.now + report = described_class.new(provider: "test_provider", limits: [], fetched_at: t) + h = report.to_h + expect(h[:provider]).to eq("test_provider") + expect(h[:limits]).to eq([]) + expect(h[:fetched_at]).to eq(t) + expect(h[:metadata]).to eq({}) + expect(h[:raw]).to be_nil + end + + it "struct equality holds when values are the same" do + t = Time.now + a = described_class.new(provider: "p", limits: [], fetched_at: t) + b = described_class.new(provider: "p", limits: [], fetched_at: t) + expect(a).to eq(b) + end + end +end |
