spec/edit_session_spec.cr @ trunk
spec
scale = 0.75 fontscale = 1.500 box wid 1.380 ht 0.340 with .nw at (0.035, -0.035) fill 0xCBD5E1 color 0xCBD5E1 box wid 1.380 ht 0.340 with .nw at (0.000, 0.000) fill 0x7C83E6 color 0x7C83E6 box wid 0.300 ht 0.065 rad 0.018 at (0.370, -0.022) fill 0xffffff color 0xffffff line from (0.140, 0.000) to (0.140, -0.340) color white line from (1.240, 0.000) to (1.240, -0.340) color white text "require 'spec'" color white at (0.690, -0.170)→ /pikchrshow
crystal source
require "spec"
json block
{
"kind": "require",
"name": "spec",
"doc": null,
"children": []
}
file_utils
scale = 0.75 fontscale = 1.500 box wid 1.645 ht 0.340 with .nw at (0.035, -0.455) fill 0xCBD5E1 color 0xCBD5E1 box wid 1.645 ht 0.340 with .nw at (0.000, -0.420) fill 0x7C83E6 color 0x7C83E6 box wid 0.300 ht 0.065 rad 0.018 at (0.370, -0.443) fill 0xffffff color 0xffffff line from (0.140, -0.420) to (0.140, -0.760) color white line from (1.505, -0.420) to (1.505, -0.760) color white text "require 'file_utils'" color white at (0.823, -0.590)→ /pikchrshow
crystal source
require "file_utils"
json block
{
"kind": "require",
"name": "file_utils",
"doc": null,
"children": []
}
../src/gpcr/edit
scale = 0.75 fontscale = 1.500 box wid 2.117 ht 0.340 with .nw at (0.035, -0.875) fill 0xCBD5E1 color 0xCBD5E1 box wid 2.117 ht 0.340 with .nw at (0.000, -0.840) fill 0x7C83E6 color 0x7C83E6 box wid 0.300 ht 0.065 rad 0.018 at (0.370, -0.863) fill 0xffffff color 0xffffff line from (0.140, -0.840) to (0.140, -1.180) color white line from (1.977, -0.840) to (1.977, -1.180) color white text "require '../src/gpcr/edit'" color white at (1.059, -1.010)→ /pikchrshow
crystal source
require "../src/gpcr/edit"
json block
{
"kind": "require",
"name": "../src/gpcr/edit",
"doc": null,
"children": []
}
private FOSSIL_ENV = {"USER" => "gpcr-spec"}
# Creates a throwaway repo with one committed file, returns {repo, rev}.
# rev is the checkout hash of the seeding commit (the file's base_revision).
private def make_repo_with_file(file_path : String, content : String) : {repo: String, rev: String, checkout_dir: String}
repo = File.tempname("gpcr_session_test", ".fossil")
checkout_dir = File.tempname("gpcr_session_test_co")
Dir.mkdir(checkout_dir)
Process.run("fossil", ["init", repo], env: FOSSIL_ENV, error: Process::Redirect::Close)
Process.run("fossil", ["open", repo], chdir: checkout_dir, env: FOSSIL_ENV, error: Process::Redirect::Close)
File.write(File.join(checkout_dir, file_path), content)
Process.run("fossil", ["add", file_path], chdir: checkout_dir, env: FOSSIL_ENV, error: Process::Redirect::Close)
Process.run("fossil", ["commit", "-m", "seed", "--no-warnings"], chdir: checkout_dir, env: FOSSIL_ENV, error: Process::Redirect::Close)
info_out = IO::Memory.new
Process.run("fossil", ["info"], chdir: checkout_dir, env: FOSSIL_ENV, output: info_out, error: Process::Redirect::Close)
rev = info_out.to_s.lines.find(&.starts_with?("checkout:")).try { |l| l.split(/\s+/)[1]? } || ""
{repo: repo, rev: rev, checkout_dir: checkout_dir}
end
# Commits a new revision of file_path in an already-open checkout, returns
# the new checkout hash. Used to simulate the file changing out from under
# a bound session (the stale-base-revision scenario).
private def commit_new_revision(checkout_dir : String, file_path : String, content : String) : String
File.write(File.join(checkout_dir, file_path), content)
Process.run("fossil", ["commit", "-m", "change", "--no-warnings"], chdir: checkout_dir, env: FOSSIL_ENV, error: Process::Redirect::Close)
info_out = IO::Memory.new
Process.run("fossil", ["info"], chdir: checkout_dir, env: FOSSIL_ENV, output: info_out, error: Process::Redirect::Close)
info_out.to_s.lines.find(&.starts_with?("checkout:")).try { |l| l.split(/\s+/)[1]? } || ""
end
private def cleanup_repo(paths : {repo: String, rev: String, checkout_dir: String})
File.delete(paths[:repo]) if File.exists?(paths[:repo])
FileUtils.rm_rf(paths[:checkout_dir])
end
FIXTURE_SRC = File.read(File.join(__DIR__, "fixtures", "edit_fixture.cr"))
describe "GPCR::Edit editing session lifecycle" do
describe "(1) bind" do
it "creates a session with generation 1, the given file/revision, not closed" do
paths = make_repo_with_file("blink.cr", FIXTURE_SRC)
begin
session = session_bind(paths[:repo], "blink.cr", paths[:rev])
session.file_path.should eq("blink.cr")
session.base_revision.should eq(paths[:rev])
session.generation.should eq(1)
session.closed.should be_false
session.session_id.should_not be_empty
ensure
cleanup_repo(paths)
end
end
it "persists the session — a fresh load by id returns the same bind" do
paths = make_repo_with_file("blink.cr", FIXTURE_SRC)
begin
session = session_bind(paths[:repo], "blink.cr", paths[:rev])
reloaded = session_load(paths[:repo], session.session_id)
reloaded.should_not be_nil
reloaded.not_nil!.file_path.should eq("blink.cr")
reloaded.not_nil!.generation.should eq(1)
ensure
cleanup_repo(paths)
end
end
end
describe "(2) one successful phrase-to-JSON edit" do
it "wrap block 8 with repeat 3 applies through a bound session" do
paths = make_repo_with_file("blink.cr", FIXTURE_SRC)
begin
session = session_bind(paths[:repo], "blink.cr", paths[:rev])
result = session_edit(paths[:repo], session.session_id, "wrap block 8 with repeat 3", "ohi")
result.status.should eq(GPCR::Edit::SessionStatus::Ok)
result.source.should_not be_nil
result.source.not_nil!.should contain("3.times do")
result.op_json.should_not be_nil
result.op_json.not_nil!.should_not be_empty
ensure
cleanup_repo(paths)
end
end
end
describe "(3) invalid operation" do
it "wrapping a def target (block 1) is rejected, not silently applied" do
paths = make_repo_with_file("blink.cr", FIXTURE_SRC)
begin
session = session_bind(paths[:repo], "blink.cr", paths[:rev])
result = session_edit(paths[:repo], session.session_id, "wrap block 1 with repeat 3", "ohi")
result.status.should eq(GPCR::Edit::SessionStatus::Invalid)
result.message.should contain("definition")
result.source.should be_nil
ensure
cleanup_repo(paths)
end
end
it "an unparseable phrase is rejected with the parser's hint" do
paths = make_repo_with_file("blink.cr", FIXTURE_SRC)
begin
session = session_bind(paths[:repo], "blink.cr", paths[:rev])
result = session_edit(paths[:repo], session.session_id, "asdf not a real phrase", "ohi")
result.status.should eq(GPCR::Edit::SessionStatus::Invalid)
result.message.should_not be_empty
ensure
cleanup_repo(paths)
end
end
end
describe "(4) stale base revision" do
it "rejects an edit once the file has changed since bind" do
paths = make_repo_with_file("blink.cr", FIXTURE_SRC)
begin
session = session_bind(paths[:repo], "blink.cr", paths[:rev])
commit_new_revision(paths[:checkout_dir], "blink.cr", FIXTURE_SRC + "\n# changed underneath the session\n")
result = session_edit(paths[:repo], session.session_id, "wrap block 8 with repeat 3", "ohi")
result.status.should eq(GPCR::Edit::SessionStatus::Stale)
result.message.should contain("stale")
ensure
cleanup_repo(paths)
end
end
it "a session bound AFTER the change is not stale" do
paths = make_repo_with_file("blink.cr", FIXTURE_SRC)
begin
new_rev = commit_new_revision(paths[:checkout_dir], "blink.cr", FIXTURE_SRC + "\n# changed before bind\n")
session = session_bind(paths[:repo], "blink.cr", new_rev)
result = session_edit(paths[:repo], session.session_id, "wrap block 8 with repeat 3", "ohi")
result.status.should eq(GPCR::Edit::SessionStatus::Ok)
ensure
cleanup_repo(paths)
end
end
end
describe "(5) capability denial" do
it "rejects an edit when caps lack check-in (i)" do
paths = make_repo_with_file("blink.cr", FIXTURE_SRC)
begin
session = session_bind(paths[:repo], "blink.cr", paths[:rev])
result = session_edit(paths[:repo], session.session_id, "wrap block 8 with repeat 3", "oh")
result.status.should eq(GPCR::Edit::SessionStatus::Denied)
ensure
cleanup_repo(paths)
end
end
it "the 's' superuser capability is still sufficient without literal 'i'" do
paths = make_repo_with_file("blink.cr", FIXTURE_SRC)
begin
session = session_bind(paths[:repo], "blink.cr", paths[:rev])
result = session_edit(paths[:repo], session.session_id, "wrap block 8 with repeat 3", "s")
result.status.should eq(GPCR::Edit::SessionStatus::Ok)
ensure
cleanup_repo(paths)
end
end
end
describe "(6) close" do
it "closes an open session" do
paths = make_repo_with_file("blink.cr", FIXTURE_SRC)
begin
session = session_bind(paths[:repo], "blink.cr", paths[:rev])
session_close(paths[:repo], session.session_id).should be_true
reloaded = session_load(paths[:repo], session.session_id)
reloaded.not_nil!.closed.should be_true
ensure
cleanup_repo(paths)
end
end
it "rejects further edits through a closed session" do
paths = make_repo_with_file("blink.cr", FIXTURE_SRC)
begin
session = session_bind(paths[:repo], "blink.cr", paths[:rev])
session_close(paths[:repo], session.session_id)
result = session_edit(paths[:repo], session.session_id, "wrap block 8 with repeat 3", "ohi")
result.status.should eq(GPCR::Edit::SessionStatus::Closed)
ensure
cleanup_repo(paths)
end
end
end
describe "(7) rebind" do
it "creates a new session with generation + 1 for the same file" do
paths = make_repo_with_file("blink.cr", FIXTURE_SRC)
begin
first = session_bind(paths[:repo], "blink.cr", paths[:rev])
session_close(paths[:repo], first.session_id)
second = session_rebind(paths[:repo], first.session_id, paths[:rev])
second.should_not be_nil
second.not_nil!.file_path.should eq("blink.cr")
second.not_nil!.generation.should eq(2)
second.not_nil!.closed.should be_false
second.not_nil!.session_id.should_not eq(first.session_id)
ensure
cleanup_repo(paths)
end
end
it "the rebound session accepts edits even though the prior one is closed" do
paths = make_repo_with_file("blink.cr", FIXTURE_SRC)
begin
first = session_bind(paths[:repo], "blink.cr", paths[:rev])
session_close(paths[:repo], first.session_id)
second = session_rebind(paths[:repo], first.session_id, paths[:rev]).not_nil!
result = session_edit(paths[:repo], second.session_id, "wrap block 8 with repeat 3", "ohi")
result.status.should eq(GPCR::Edit::SessionStatus::Ok)
ensure
cleanup_repo(paths)
end
end
end
end