|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +RSpec.describe OAuth::TTY::Command do |
| 4 | + let(:stdout) { StringIO.new } |
| 5 | + let(:stdin) { StringIO.new } |
| 6 | + let(:stderr) { StringIO.new } |
| 7 | + |
| 8 | + # Minimal concrete subclass to enable exercising #run paths |
| 9 | + class TestCommand < described_class |
| 10 | + attr_writer :required |
| 11 | + |
| 12 | + def initialize(stdout, stdin, stderr, arguments) |
| 13 | + super |
| 14 | + @required ||= [] |
| 15 | + end |
| 16 | + |
| 17 | + def required_options |
| 18 | + @required |
| 19 | + end |
| 20 | + |
| 21 | + def _run |
| 22 | + puts "ran" # use provided stdout |
| 23 | + end |
| 24 | + end |
| 25 | + |
| 26 | + def build_cmd(args = []) |
| 27 | + TestCommand.new(stdout, stdin, stderr, args) |
| 28 | + end |
| 29 | + |
| 30 | + describe "#run", :check_output do |
| 31 | + it "executes _run when required options are present" do |
| 32 | + cmd = build_cmd(["--consumer-key", "ck"]) |
| 33 | + cmd.required = [:oauth_consumer_key] |
| 34 | + out_before = stdout.string.dup |
| 35 | + cmd.run |
| 36 | + stdout.rewind |
| 37 | + expect(stdout.read).to eq(out_before + "ran\n") |
| 38 | + end |
| 39 | + |
| 40 | + it "prints missing options and help when required options are absent" do |
| 41 | + cmd = build_cmd([]) |
| 42 | + cmd.required = [:oauth_consumer_key] |
| 43 | + |
| 44 | + expect(OAuth::TTY::CLI).to receive(:puts_red).with("Options missing to OAuth CLI: --oauth_consumer_key") |
| 45 | + cmd.run |
| 46 | + stdout.rewind |
| 47 | + expect(stdout.read).to match(/Usage: oauth <command> \[ARGS\]/) |
| 48 | + end |
| 49 | + end |
| 50 | + |
| 51 | + describe "option parser defaults and flags" do |
| 52 | + it "sets sane defaults" do |
| 53 | + cmd = build_cmd([]) |
| 54 | + expect(cmd.send(:options)).to include( |
| 55 | + oauth_signature_method: "HMAC-SHA1", |
| 56 | + oauth_version: "1.0", |
| 57 | + scheme: :header, |
| 58 | + method: :post, |
| 59 | + params: [], |
| 60 | + version: "1.0" |
| 61 | + ) |
| 62 | + # Non-deterministic values are present but not asserted for exact value |
| 63 | + expect(cmd.send(:options)).to include(:oauth_nonce, :oauth_timestamp) |
| 64 | + end |
| 65 | + |
| 66 | + it "switches scheme based on -B/--body, -H/--header, -Q/--query-string" do |
| 67 | + expect(build_cmd(["-B"]).send(:options)[:scheme]).to eq(:body) |
| 68 | + expect(build_cmd(["-H"]).send(:options)[:scheme]).to eq(:header) |
| 69 | + expect(build_cmd(["-Q"]).send(:options)[:scheme]).to eq(:query_string) |
| 70 | + end |
| 71 | + |
| 72 | + it "captures verbosity and xmpp flags" do |
| 73 | + expect(build_cmd(["--verbose"]).send(:options)[:verbose]).to be true |
| 74 | + expect(build_cmd(["--xmpp"]).send(:options)).to include(xmpp: true) |
| 75 | + # Exercise predicate helpers |
| 76 | + cmd = build_cmd(["--xmpp", "--verbose"]) |
| 77 | + expect(cmd.send(:xmpp?)).to be true |
| 78 | + expect(cmd.send(:verbose?)).to be true |
| 79 | + end |
| 80 | + |
| 81 | + it "collects sign/query related switches" do |
| 82 | + cmd = build_cmd(%w[ |
| 83 | + --method GET |
| 84 | + --nonce N |
| 85 | + --parameters a:1 |
| 86 | + --parameters raw_pair |
| 87 | + --signature-method PLAINTEXT |
| 88 | + --token T |
| 89 | + --secret S |
| 90 | + --timestamp TS |
| 91 | + --realm R |
| 92 | + --uri http://example.com/ |
| 93 | + --version 1.0a |
| 94 | + ]) |
| 95 | + expect(cmd.send(:options)).to include( |
| 96 | + method: "GET", |
| 97 | + oauth_nonce: "N", |
| 98 | + oauth_signature_method: "PLAINTEXT", |
| 99 | + oauth_token: "T", |
| 100 | + oauth_token_secret: "S", |
| 101 | + oauth_timestamp: "TS", |
| 102 | + realm: "R", |
| 103 | + uri: "http://example.com/", |
| 104 | + oauth_version: "1.0a" |
| 105 | + ) |
| 106 | + expect(cmd.send(:options)[:params]).to eq(["a:1", "raw_pair"]) |
| 107 | + end |
| 108 | + |
| 109 | + it "honors --no-version by nulling oauth_version" do |
| 110 | + cmd = build_cmd(["--no-version"]) |
| 111 | + expect(cmd.send(:options)[:oauth_version]).to be_nil |
| 112 | + end |
| 113 | + |
| 114 | + it "captures authorization URLs and scope" do |
| 115 | + cmd = build_cmd(%w[ |
| 116 | + --access-token-url https://example.com/access |
| 117 | + --authorize-url https://example.com/auth |
| 118 | + --callback-url https://example.com/cb |
| 119 | + --request-token-url https://example.com/request |
| 120 | + --scope email |
| 121 | + ]) |
| 122 | + expect(cmd.send(:options)).to include( |
| 123 | + access_token_url: "https://example.com/access", |
| 124 | + authorize_url: "https://example.com/auth", |
| 125 | + oauth_callback: "https://example.com/cb", |
| 126 | + request_token_url: "https://example.com/request", |
| 127 | + scope: "email" |
| 128 | + ) |
| 129 | + end |
| 130 | + end |
| 131 | + |
| 132 | + describe "#parameters" do |
| 133 | + it "builds escaped params and merges oauth keys, dropping nil/empty ones" do |
| 134 | + cmd = build_cmd(%w[ |
| 135 | + --consumer-key CK |
| 136 | + --token TK |
| 137 | + --parameters foo:bar |
| 138 | + --parameters baz:qux |
| 139 | + --parameters raw=pair |
| 140 | + ]) |
| 141 | + params = cmd.send(:parameters) |
| 142 | + # CGI.parse returns arrays of values per key |
| 143 | + expect(params["foo"]).to eq(["bar"]) # escaped colon-pair |
| 144 | + expect(params["baz"]).to eq(["qux"]) # escaped colon-pair |
| 145 | + expect(params["raw"]).to eq(["pair"]) # raw pair preserved |
| 146 | + # OAuth fields present |
| 147 | + expect(params).to include( |
| 148 | + "oauth_consumer_key" => "CK", |
| 149 | + "oauth_token" => "TK", |
| 150 | + "oauth_signature_method" => "HMAC-SHA1" |
| 151 | + ) |
| 152 | + # timestamp, nonce exist but are not asserted exactly |
| 153 | + expect(params).to include("oauth_timestamp", "oauth_nonce") |
| 154 | + end |
| 155 | + end |
| 156 | + |
| 157 | + describe "output helpers", :check_output do |
| 158 | + it "writes to stdout and stderr" do |
| 159 | + cmd = build_cmd([]) |
| 160 | + cmd.send(:puts, "out!") |
| 161 | + cmd.send(:alert, "err!") |
| 162 | + stdout.rewind |
| 163 | + stderr.rewind |
| 164 | + expect(stdout.read).to eq("out!\n") |
| 165 | + expect(stderr.read).to eq("err!\n") |
| 166 | + end |
| 167 | + end |
| 168 | +end |
0 commit comments