2021-02-22 17:27:13 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'spec_helper'
|
|
|
|
|
|
|
|
RSpec.describe Pages::ZipDirectoryService do
|
|
|
|
around do |example|
|
|
|
|
Dir.mktmpdir do |dir|
|
|
|
|
@work_dir = dir
|
|
|
|
example.run
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
let(:ignore_invalid_entries) { false }
|
|
|
|
|
2021-04-29 21:17:54 +05:30
|
|
|
let(:service_directory) { @work_dir }
|
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
let(:service) do
|
2021-04-29 21:17:54 +05:30
|
|
|
described_class.new(service_directory, ignore_invalid_entries: ignore_invalid_entries)
|
2021-03-11 19:13:27 +05:30
|
|
|
end
|
|
|
|
|
2021-02-22 17:27:13 +05:30
|
|
|
let(:result) do
|
2021-03-11 19:13:27 +05:30
|
|
|
service.execute
|
2021-02-22 17:27:13 +05:30
|
|
|
end
|
|
|
|
|
2021-03-08 18:12:59 +05:30
|
|
|
let(:status) { result[:status] }
|
|
|
|
let(:message) { result[:message] }
|
|
|
|
let(:archive) { result[:archive_path] }
|
|
|
|
let(:entries_count) { result[:entries_count] }
|
|
|
|
|
2021-04-29 21:17:54 +05:30
|
|
|
shared_examples 'handles invalid public directory' do
|
|
|
|
it 'returns success' do
|
|
|
|
expect(status).to eq(:success)
|
|
|
|
expect(archive).to be_nil
|
|
|
|
expect(entries_count).to be_nil
|
|
|
|
end
|
2021-03-08 18:12:59 +05:30
|
|
|
end
|
|
|
|
|
2021-04-29 21:17:54 +05:30
|
|
|
context "when work direcotry doesn't exist" do
|
|
|
|
let(:service_directory) { "/tmp/not/existing/dir" }
|
2021-02-22 17:27:13 +05:30
|
|
|
|
2021-04-29 21:17:54 +05:30
|
|
|
include_examples 'handles invalid public directory'
|
2021-02-22 17:27:13 +05:30
|
|
|
end
|
|
|
|
|
2021-04-29 21:17:54 +05:30
|
|
|
context 'when public directory is absent' do
|
|
|
|
include_examples 'handles invalid public directory'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when public directory is a symlink' do
|
|
|
|
before do
|
|
|
|
create_dir('target')
|
|
|
|
create_file('./target/index.html', 'hello')
|
|
|
|
create_link("public", "./target")
|
|
|
|
end
|
2021-02-22 17:27:13 +05:30
|
|
|
|
2021-04-29 21:17:54 +05:30
|
|
|
include_examples 'handles invalid public directory'
|
2021-02-22 17:27:13 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'when there is a public directory' do
|
|
|
|
before do
|
|
|
|
create_dir('public')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'creates the file next the public directory' do
|
|
|
|
expect(archive).to eq(File.join(@work_dir, "@migrated.zip"))
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'includes public directory' do
|
|
|
|
with_zip_file do |zip_file|
|
|
|
|
entry = zip_file.get_entry("public/")
|
|
|
|
expect(entry.ftype).to eq(:directory)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns number of entries' do
|
|
|
|
create_file("public/index.html", "hello")
|
|
|
|
create_link("public/link.html", "./index.html")
|
|
|
|
expect(entries_count).to eq(3) # + 'public' directory
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'removes the old file if it exists' do
|
|
|
|
# simulate the old run
|
|
|
|
described_class.new(@work_dir).execute
|
|
|
|
|
|
|
|
with_zip_file do |zip_file|
|
|
|
|
expect(zip_file.entries.count).to eq(1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'ignores other top level files and directories' do
|
|
|
|
create_file("top_level.html", "hello")
|
|
|
|
create_dir("public2")
|
|
|
|
|
|
|
|
with_zip_file do |zip_file|
|
|
|
|
expect { zip_file.get_entry("top_level.html") }.to raise_error(Errno::ENOENT)
|
|
|
|
expect { zip_file.get_entry("public2/") }.to raise_error(Errno::ENOENT)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'includes index.html file' do
|
|
|
|
create_file("public/index.html", "Hello!")
|
|
|
|
|
|
|
|
with_zip_file do |zip_file|
|
|
|
|
entry = zip_file.get_entry("public/index.html")
|
|
|
|
expect(zip_file.read(entry)).to eq("Hello!")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'includes hidden file' do
|
|
|
|
create_file("public/.hidden.html", "Hello!")
|
|
|
|
|
|
|
|
with_zip_file do |zip_file|
|
|
|
|
entry = zip_file.get_entry("public/.hidden.html")
|
|
|
|
expect(zip_file.read(entry)).to eq("Hello!")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'includes nested directories and files' do
|
|
|
|
create_dir("public/nested")
|
|
|
|
create_dir("public/nested/nested2")
|
|
|
|
create_file("public/nested/nested2/nested.html", "Hello nested")
|
|
|
|
|
|
|
|
with_zip_file do |zip_file|
|
|
|
|
entry = zip_file.get_entry("public/nested")
|
|
|
|
expect(entry.ftype).to eq(:directory)
|
|
|
|
|
|
|
|
entry = zip_file.get_entry("public/nested/nested2")
|
|
|
|
expect(entry.ftype).to eq(:directory)
|
|
|
|
|
|
|
|
entry = zip_file.get_entry("public/nested/nested2/nested.html")
|
|
|
|
expect(zip_file.read(entry)).to eq("Hello nested")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'adds a valid symlink' do
|
|
|
|
create_file("public/target.html", "hello")
|
|
|
|
create_link("public/link.html", "./target.html")
|
|
|
|
|
|
|
|
with_zip_file do |zip_file|
|
|
|
|
entry = zip_file.get_entry("public/link.html")
|
|
|
|
expect(entry.ftype).to eq(:symlink)
|
|
|
|
expect(zip_file.read(entry)).to eq("./target.html")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
shared_examples "raises or ignores file" do |raised_exception, file|
|
|
|
|
it 'raises error' do
|
|
|
|
expect do
|
|
|
|
result
|
|
|
|
end.to raise_error(raised_exception)
|
|
|
|
end
|
2021-02-22 17:27:13 +05:30
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
context 'when errors are ignored' do
|
|
|
|
let(:ignore_invalid_entries) { true }
|
|
|
|
|
|
|
|
it 'does not create entry' do
|
|
|
|
with_zip_file do |zip_file|
|
|
|
|
expect { zip_file.get_entry(file) }.to raise_error(Errno::ENOENT)
|
|
|
|
end
|
|
|
|
end
|
2021-02-22 17:27:13 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
context 'when symlink points outside of public directory' do
|
|
|
|
before do
|
|
|
|
create_file("target.html", "hello")
|
|
|
|
create_link("public/link.html", "../target.html")
|
|
|
|
end
|
2021-02-22 17:27:13 +05:30
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
include_examples "raises or ignores file", described_class::InvalidEntryError, "public/link.html"
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when target of the symlink is absent' do
|
|
|
|
before do
|
|
|
|
create_link("public/link.html", "./target.html")
|
2021-02-22 17:27:13 +05:30
|
|
|
end
|
2021-03-11 19:13:27 +05:30
|
|
|
|
|
|
|
include_examples "raises or ignores file", Errno::ENOENT, "public/link.html"
|
2021-02-22 17:27:13 +05:30
|
|
|
end
|
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
context 'when targets itself' do
|
|
|
|
before do
|
|
|
|
create_link("public/link.html", "./link.html")
|
|
|
|
end
|
2021-02-22 17:27:13 +05:30
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
include_examples "raises or ignores file", Errno::ELOOP, "public/link.html"
|
|
|
|
end
|
2021-02-22 17:27:13 +05:30
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
context 'when symlink is absolute and points to outside of directory' do
|
|
|
|
before do
|
|
|
|
target = File.join(@work_dir, "target")
|
|
|
|
FileUtils.touch(target)
|
|
|
|
|
|
|
|
create_link("public/link.html", target)
|
2021-02-22 17:27:13 +05:30
|
|
|
end
|
2021-03-11 19:13:27 +05:30
|
|
|
|
|
|
|
include_examples "raises or ignores file", described_class::InvalidEntryError, "public/link.html"
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when entry has unknown ftype' do
|
|
|
|
before do
|
|
|
|
file = create_file("public/index.html", "hello")
|
|
|
|
|
|
|
|
allow(File).to receive(:lstat).and_call_original
|
|
|
|
expect(File).to receive(:lstat).with(file) { double("lstat", ftype: "unknown") }
|
|
|
|
end
|
|
|
|
|
|
|
|
include_examples "raises or ignores file", described_class::InvalidEntryError, "public/index.html"
|
2021-02-22 17:27:13 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it "includes raw symlink if it's target is a valid directory" do
|
|
|
|
create_dir("public/target")
|
|
|
|
create_file("public/target/index.html", "hello")
|
|
|
|
create_link("public/link", "./target")
|
|
|
|
|
|
|
|
with_zip_file do |zip_file|
|
|
|
|
expect(zip_file.entries.count).to eq(4) # /public and 3 created above
|
|
|
|
|
|
|
|
entry = zip_file.get_entry("public/link")
|
|
|
|
expect(entry.ftype).to eq(:symlink)
|
|
|
|
expect(zip_file.read(entry)).to eq("./target")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "validating fixtures pages archives" do
|
|
|
|
using RSpec::Parameterized::TableSyntax
|
|
|
|
|
|
|
|
where(:fixture_path) do
|
|
|
|
["spec/fixtures/pages.zip", "spec/fixtures/pages_non_writeable.zip"]
|
|
|
|
end
|
|
|
|
|
|
|
|
with_them do
|
|
|
|
let(:full_fixture_path) { Rails.root.join(fixture_path) }
|
|
|
|
|
|
|
|
it 'a created archives contains exactly the same entries' do
|
|
|
|
SafeZip::Extract.new(full_fixture_path).extract(directories: ['public'], to: @work_dir)
|
|
|
|
|
|
|
|
with_zip_file do |created_archive|
|
|
|
|
Zip::File.open(full_fixture_path) do |original_archive|
|
|
|
|
original_archive.entries do |original_entry|
|
|
|
|
created_entry = created_archive.get_entry(original_entry.name)
|
|
|
|
|
|
|
|
expect(created_entry.name).to eq(original_entry.name)
|
|
|
|
expect(created_entry.ftype).to eq(original_entry.ftype)
|
|
|
|
expect(created_archive.read(created_entry)).to eq(original_archive.read(original_entry))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def create_file(name, content)
|
2021-03-11 19:13:27 +05:30
|
|
|
file_path = File.join(@work_dir, name)
|
|
|
|
|
|
|
|
File.open(file_path, "w") do |f|
|
2021-02-22 17:27:13 +05:30
|
|
|
f.write(content)
|
|
|
|
end
|
2021-03-11 19:13:27 +05:30
|
|
|
|
|
|
|
file_path
|
2021-02-22 17:27:13 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def create_dir(dir)
|
|
|
|
Dir.mkdir(File.join(@work_dir, dir))
|
|
|
|
end
|
|
|
|
|
|
|
|
def create_link(new_name, target)
|
|
|
|
File.symlink(target, File.join(@work_dir, new_name))
|
|
|
|
end
|
|
|
|
|
|
|
|
def with_zip_file
|
|
|
|
Zip::File.open(archive) do |zip_file|
|
|
|
|
yield zip_file
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|