|
4 | 4 | describe "#initialize" do |
5 | 5 | it "detects ffmpeg binary" do |
6 | 6 | config = described_class.new |
7 | | - expect(config.ffmpeg_binary).not_to be_nil |
8 | 7 | expect(config.ffmpeg_binary).to include("ffmpeg") |
9 | 8 | end |
10 | 9 |
|
11 | 10 | it "detects ffprobe binary" do |
12 | 11 | config = described_class.new |
13 | | - expect(config.ffprobe_binary).not_to be_nil |
14 | 12 | expect(config.ffprobe_binary).to include("ffprobe") |
15 | 13 | end |
16 | 14 |
|
17 | | - it "sets default timeout" do |
| 15 | + it "sets default timeout to 30 seconds" do |
| 16 | + expect(described_class.new.timeout).to eq(30) |
| 17 | + end |
| 18 | + end |
| 19 | + |
| 20 | + describe "#detect_binary (via initialize)" do |
| 21 | + context "when FFMPEGCORE_FFMPEG points to an executable file" do |
| 22 | + it "uses the env override" do |
| 23 | + allow(File).to receive(:executable?).and_call_original |
| 24 | + allow(File).to receive(:executable?).with("/custom/ffmpeg").and_return(true) |
| 25 | + |
| 26 | + with_env("FFMPEGCORE_FFMPEG" => "/custom/ffmpeg") do |
| 27 | + config = described_class.new |
| 28 | + expect(config.ffmpeg_binary).to eq("/custom/ffmpeg") |
| 29 | + end |
| 30 | + end |
| 31 | + end |
| 32 | + |
| 33 | + context "when FFMPEGCORE_FFMPEG points to a non-executable path" do |
| 34 | + it "falls through to system lookup" do |
| 35 | + allow(File).to receive(:executable?).and_call_original |
| 36 | + allow(File).to receive(:executable?).with("/not/executable").and_return(false) |
| 37 | + |
| 38 | + with_env("FFMPEGCORE_FFMPEG" => "/not/executable") do |
| 39 | + # Should not raise — falls through to which/where and finds real ffmpeg |
| 40 | + expect { described_class.new }.not_to raise_error |
| 41 | + end |
| 42 | + end |
| 43 | + end |
| 44 | + |
| 45 | + context "when binary is not found anywhere" do |
| 46 | + before do |
| 47 | + # Make all File.executable? checks return false |
| 48 | + allow(File).to receive(:executable?).and_return(false) |
| 49 | + # Make system lookup fail |
| 50 | + allow(Open3).to receive(:capture2).and_return(["", double(success?: false)]) |
| 51 | + end |
| 52 | + |
| 53 | + # Restore real behaviour before spec_helper's global `after` runs |
| 54 | + # `reset_configuration!`, otherwise the stubs make *that* raise too. |
| 55 | + after do |
| 56 | + allow(File).to receive(:executable?).and_call_original |
| 57 | + allow(Open3).to receive(:capture2).and_call_original |
| 58 | + end |
| 59 | + |
| 60 | + it "raises BinaryNotFoundError with install instructions" do |
| 61 | + expect { described_class.new }.to raise_error( |
| 62 | + FFmpegCore::BinaryNotFoundError, |
| 63 | + /ffmpeg not found/i |
| 64 | + ) |
| 65 | + end |
| 66 | + |
| 67 | + it "includes platform hints in the error message" do |
| 68 | + expect { described_class.new }.to raise_error( |
| 69 | + FFmpegCore::BinaryNotFoundError, |
| 70 | + /brew install ffmpeg/ |
| 71 | + ) |
| 72 | + end |
| 73 | + end |
| 74 | + |
| 75 | + context "when binary is not in PATH but exists at a known location" do |
| 76 | + let(:known_ffmpeg_path) { "/opt/homebrew/bin/ffmpeg" } |
| 77 | + let(:known_ffprobe_path) { "/opt/homebrew/bin/ffprobe" } |
| 78 | + |
| 79 | + before do |
| 80 | + allow(File).to receive(:executable?).and_return(false) |
| 81 | + allow(File).to receive(:executable?).with(known_ffmpeg_path).and_return(true) |
| 82 | + allow(File).to receive(:executable?).with(known_ffprobe_path).and_return(true) |
| 83 | + allow(Open3).to receive(:capture2).and_return(["", double(success?: false)]) |
| 84 | + end |
| 85 | + |
| 86 | + # Same reason as above: restore real methods before global after hook fires. |
| 87 | + after do |
| 88 | + allow(File).to receive(:executable?).and_call_original |
| 89 | + allow(Open3).to receive(:capture2).and_call_original |
| 90 | + end |
| 91 | + |
| 92 | + it "finds the binary at the known location" do |
| 93 | + config = described_class.new |
| 94 | + expect(config.ffmpeg_binary).to eq(known_ffmpeg_path) |
| 95 | + end |
| 96 | + end |
| 97 | + end |
| 98 | + |
| 99 | + describe "#encoders" do |
| 100 | + it "returns a set of encoder names" do |
| 101 | + expect(described_class.new.encoders).to be_a(Set) |
| 102 | + end |
| 103 | + |
| 104 | + it "includes common encoders" do |
| 105 | + encoders = described_class.new.encoders |
| 106 | + expect(encoders).to include("libx264").or include("aac") |
| 107 | + end |
| 108 | + |
| 109 | + it "returns empty Set when ffmpeg binary is nil" do |
18 | 110 | config = described_class.new |
19 | | - expect(config.timeout).to eq(30) |
| 111 | + config.ffmpeg_binary = nil |
| 112 | + expect(config.encoders).to eq(Set.new) |
20 | 113 | end |
21 | 114 | end |
22 | 115 | end |
23 | 116 |
|
24 | 117 | RSpec.describe FFmpegCore do |
25 | 118 | describe ".configuration" do |
26 | | - it "returns configuration instance" do |
| 119 | + it "returns a Configuration instance" do |
27 | 120 | expect(described_class.configuration).to be_a(FFmpegCore::Configuration) |
28 | 121 | end |
| 122 | + |
| 123 | + it "returns the same instance on subsequent calls" do |
| 124 | + first = described_class.configuration |
| 125 | + second = described_class.configuration |
| 126 | + expect(first).to equal(second) |
| 127 | + end |
29 | 128 | end |
30 | 129 |
|
31 | 130 | describe ".configure" do |
32 | | - it "yields configuration block" do |
| 131 | + it "yields the configuration object" do |
33 | 132 | described_class.configure do |config| |
34 | 133 | config.timeout = 60 |
35 | 134 | end |
36 | 135 |
|
37 | 136 | expect(described_class.configuration.timeout).to eq(60) |
38 | 137 | end |
39 | 138 | end |
| 139 | + |
| 140 | + describe ".reset_configuration!" do |
| 141 | + it "resets to a fresh Configuration instance" do |
| 142 | + original = described_class.configuration |
| 143 | + described_class.reset_configuration! |
| 144 | + expect(described_class.configuration).not_to equal(original) |
| 145 | + end |
| 146 | + end |
| 147 | +end |
| 148 | + |
| 149 | +# Helpers |
| 150 | + |
| 151 | +def with_env(vars, &block) |
| 152 | + old = vars.transform_values { |key| ENV.fetch(key, nil) } |
| 153 | + vars.each { |k, v| ENV[k] = v } |
| 154 | + yield |
| 155 | +ensure |
| 156 | + vars.each { |k, _| old[k].nil? ? ENV.delete(k) : ENV.store(k, old[k]) } |
40 | 157 | end |
0 commit comments