Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions spec/flash_isolation_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
require "./spec_helper"

# Reads the flash of the request it serves, then optionally sets one for the next request.
private class FlashProbe
include HTTP::Handler

getter shown = [] of String?
property next_message : String? = nil

def initialize(@store : Session::MemoryStore(UserSession))
end

def call(context : HTTP::Server::Context)
shown << @store.flash.now["notice"]?
@store.flash.next["notice"] = next_message.not_nil! if next_message
end
end

private def request_for(session_id : String) : HTTP::Server::Context
request = HTTP::Request.new("GET", "/")
request.cookies << HTTP::Cookie.new(name: Session.config.session_key, value: session_id)
HTTP::Server::Context.new(request, HTTP::Server::Response.new(IO::Memory.new))
end

describe "flash messages" do
it "reach the session that set them, and nobody else's" do
reset_config
store = Session::MemoryStore(UserSession).new
Session.config.store = store
alice = UserSession.new
bob = UserSession.new
store[alice.session_id] = alice
store[bob.session_id] = bob

probe = FlashProbe.new(store)
handler = Session::SessionHandler(UserSession).new(store)
handler.next = probe

probe.next_message = "Alice's private notice"
handler.call(request_for(alice.session_id))
probe.next_message = nil
handler.call(request_for(bob.session_id))
handler.call(request_for(alice.session_id))

probe.shown.should eq [nil, nil, "Alice's private notice"]
end

it "lasts for exactly one request" do
reset_config
store = Session::MemoryStore(UserSession).new
Session.config.store = store
alice = UserSession.new
store[alice.session_id] = alice

probe = FlashProbe.new(store)
handler = Session::SessionHandler(UserSession).new(store)
handler.next = probe

probe.next_message = "once"
handler.call(request_for(alice.session_id))
probe.next_message = nil
2.times { handler.call(request_for(alice.session_id)) }

probe.shown.should eq [nil, "once", nil]
end

it "is saved with the session, so a store that serializes it keeps it" do
session = UserSession.new
session.flash.next["notice"] = "saved"

UserSession.from_json(session.to_json).flash.next["notice"].should eq "saved"
end
end
7 changes: 5 additions & 2 deletions spec/store_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,14 @@ describe Session::Store do
store.flash.should be_a Session::Flash
end

it "rotates flash on load" do
it "rotates the flash of the session it loads" do
store = Session::MemoryStore(UserSession).new
store.flash["notice"] = "Hello"
session = UserSession.new
session.flash["notice"] = "Hello"
store[session.session_id] = session

cookies = HTTP::Cookies.new
cookies << HTTP::Cookie.new(name: store.session_key, value: session.session_id)
store.load_from(cookies)

store.flash.now["notice"].should eq "Hello"
Expand Down
4 changes: 4 additions & 0 deletions src/base.cr
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ module Session

property expires_at : Time = Session.config.timeout.from_now

# Messages for the next request. They belong to the session, so they are saved with it
# and reach only the visitor they were meant for.
getter flash : Flash = Flash.new

def initialize
end

Expand Down
11 changes: 5 additions & 6 deletions src/store.cr
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,10 @@ module Session
# Instance variables for session lifecycle
@mutex : Mutex = Mutex.new
@current_sessions = {} of Fiber => T
@flash : Flash = Flash.new

# Access flash messages
# The current request's flash messages (those of its session).
def flash : Flash
@flash
current_session.flash
end

def session_id : String
Expand Down Expand Up @@ -116,9 +115,6 @@ module Session
end

def load_from(request_cookies : HTTP::Cookies) : T?
# Rotate flash messages at the start of each request
@flash.rotate!

if self.is_a?(CookieStore(T))
self.as(CookieStore(T)).cookies = request_cookies
end
Expand All @@ -132,6 +128,9 @@ module Session
current_session.touch
end

# Messages set by the previous request become this request's
current_session.flash.rotate!

on(:loaded, session_id, current_session)
end
end
Expand Down
Loading