1-module(yaws_sessions_server_SUITE).
2
3-include("testsuite.hrl").
4
5-compile(export_all).
6
7all() ->
8    [
9     {group, basic_tests},
10     {group, cookiegen_tests}
11    ].
12
13groups() ->
14    [
15     {basic_tests, [], [init,
16                        new_session_and_list,
17                        replace_session,
18                        replace_session_cleanup,
19                        cookieval_to_opaque,
20                        delete_session,
21                        timeout]},
22     {cookiegen_tests, [], [new_cookiegen_session]}
23    ].
24
25%%====================================================================
26init_per_suite(Config) ->
27    application:load(yaws),
28    Config.
29
30end_per_suite(_Config) ->
31    application:unload(yaws),
32    ok.
33
34init_per_group(cookiegen_tests, Config) ->
35    Id       = "testsuite-server",
36    YConf    = filename:join(?tempdir(?MODULE), "yaws.conf"),
37    YawsHome = ?tempdir(?MODULE),
38    os:putenv("YAWSHOME", YawsHome),
39    application:load(yaws),
40    application:set_env(yaws, id,   Id),
41    application:set_env(yaws, conf, YConf),
42    ok = yaws:start(),
43    {ok, GConf, SCs} = yaws_api:getconf(),
44    yaws_api:setconf(yaws:gconf_ysession_cookiegen(GConf, ?MODULE), SCs),
45    Config;
46init_per_group(_Group, Config) ->
47    Config.
48
49end_per_group(cookiegen_tests, _Config) ->
50    ok = application:stop(yaws);
51end_per_group(_Group, _Config) ->
52    ok.
53
54init_per_testcase(_Test, Config) ->
55    Config.
56
57end_per_testcase(_Test, _Config) ->
58    ok.
59
60%%====================================================================
61%% Change this macro to test another backend storage module
62%%-define(BACKEND, ?MODULE).
63-define(BACKEND, yaws_session_server).
64
65start() ->
66    %% starting crypto is required for these tests to pass on R13
67    {ok, Pid} = gen_server:start({local, yaws_session_server},
68                                 yaws_session_server, ?BACKEND, []),
69    ?BACKEND:cleanup(),
70    {ok, Pid}.
71
72init(_Config) ->
73    {ok, _} = start(),
74    ?assertEqual([], ?BACKEND:list()),
75    yaws_session_server:stop(),
76    ok.
77
78new_session_and_list(_Config) ->
79    {ok, _} = start(),
80    ?assertMatch([], ?BACKEND:list()),
81    Cookie1 = yaws_session_server:new_session({opaque, 1}),
82    Cookie2 = yaws_session_server:new_session({opaque, 2}, 1, self()),
83    Cookie3 = yaws_session_server:new_session({opaque, 3}, 1, self(), "cookie"),
84    ?assert(Cookie1 /= Cookie2),
85    CPrefix = atom_to_list(node()) ++ "-",
86    ?assert(lists:prefix(CPrefix, Cookie1)),
87    ?assert(lists:prefix(CPrefix, Cookie2)),
88    ?assertEqual("cookie", Cookie3),
89    ?assertMatch([_, _, _], ?BACKEND:list()),
90    yaws_session_server:stop(),
91    ok.
92
93replace_session(_Config) ->
94    {ok, _} = start(),
95    Opaque = {opaque, 1},
96    Cookie1 = yaws_session_server:new_session(Opaque),
97    [Session] = ?BACKEND:list(),
98    Opaque2 = {opaque, 2},
99    ?assert(yaws_session_server:replace_session(Cookie1, Opaque2)),
100    [Session_updated] = ?BACKEND:list(),
101    ?assert(Session /= Session_updated),
102    yaws_session_server:stop(),
103    ok.
104
105replace_session_cleanup(_Config) ->
106    {ok, _} = start(),
107    Parent = self(),
108    Cleanup1 = spawn(fun() ->
109                             receive
110                                 stop -> ok;
111                                 Msg  -> Parent ! {cleanup1, Msg}
112                             end
113                     end),
114    Opaque = {opaque, 1},
115    Cookie = yaws_session_server:new_session(Opaque, 60, Cleanup1),
116    Cleanup2 = spawn(fun() ->
117                             receive
118                                 stop -> ok;
119                                 Msg  -> Parent ! {cleanup2, Msg}
120                             end
121                     end),
122    Opaque2 = {opaque, 2},
123    ?assert(yaws_session_server:replace_session(Cookie, Opaque2, Cleanup2)),
124    ?assert(is_process_alive(Cleanup1)),
125    ?assert(is_process_alive(Cleanup2)),
126    yaws_session_server:delete_session(Cookie),
127    Res = receive
128              {cleanup2, {yaws_session_end, normal, _, Opaque2}} ->
129                  ?assertNot(is_process_alive(Cleanup2)),
130                  ok
131          after
132              5000 ->
133                  {error, cleanup_failure}
134          end,
135    ?assertEqual(ok, Res),
136    ?assert(is_process_alive(Cleanup1)),
137    Cleanup1 ! stop,
138    yaws_session_server:stop(),
139    ok.
140
141cookieval_to_opaque(_Config) ->
142    {ok, _} = start(),
143    Opaque = {opaque, 1},
144    Cookie1 = yaws_session_server:new_session(Opaque),
145    [Session] = ?BACKEND:list(),
146    timer:sleep(1000), %% ensure cookie TS is updated of at least 1 second
147    {ok, Result} = yaws_session_server:cookieval_to_opaque(Cookie1),
148    ?assertEqual(Opaque, Result),
149    [Session_updated] = ?BACKEND:list(),
150    ?assert(Session /= Session_updated),
151    yaws_session_server:stop(),
152    ok.
153
154delete_session(_Config) ->
155    {ok, _} = start(),
156    Opaque = {opaque, 1},
157    Cookie1 = yaws_session_server:new_session(Opaque),
158    Cookie2 = yaws_session_server:new_session(Opaque, 10, self()),
159    ?assertMatch([_, _], ?BACKEND:list()),
160    Expected1 = nocleanup,
161    ?assertEqual(Expected1, yaws_session_server:delete_session(Cookie1)),
162    Delete_notif = {yaws_session_end, normal, Cookie2, Opaque},
163    ?assertEqual(Delete_notif, yaws_session_server:delete_session(Cookie2)),
164    ?assertEqual([], ?BACKEND:list()),
165    Res = receive
166              Delete_notif -> ok
167          after
168              500 -> cleanup_timeout
169          end,
170    ?assertEqual(ok, Res),
171    yaws_session_server:stop(),
172    ok.
173
174timeout(_Config) ->
175    {ok, Pid} = start(),
176    Opaque = {opaque, 1},
177    Cookie1 = yaws_session_server:new_session(Opaque, 0, self()),
178    Opaque2 = {opaque, 1},
179    _Cookie2 = yaws_session_server:new_session(Opaque2, 10, self()),
180    ?assertMatch([_, _], ?BACKEND:list()),
181    Pid ! timeout,
182    Timeout_notif = {yaws_session_end,timeout, Cookie1, Opaque},
183    Res =
184        receive
185            Timeout_notif -> ok
186        after
187            500 -> cleanup_timeout
188        end,
189    ?assertEqual(ok, Res),
190    ?assertMatch([_], ?BACKEND:list()),
191    yaws_session_server:stop(),
192    ok.
193
194%% Our test callbacks
195init_backend(_) ->
196    proc_lib:start (?MODULE, mock_session_server, []),
197    ok.
198
199insert(Session) ->
200    ?MODULE ! {insert, Session},
201    true.
202
203lookup(Cookie) ->
204    ?MODULE ! {{lookup, Cookie}, self()},
205    receive {ok, Opaque} -> [Opaque]
206    after 1000 -> list_timeout
207    end.
208
209list() ->
210    ?MODULE ! {list, self()},
211    receive {ok, Sessions} -> Sessions
212    after 1000 -> list_timeout
213    end.
214
215delete(Cookie) ->
216    ?MODULE ! {delete, Cookie},
217    true.
218
219cleanup() ->
220    Cleaner =
221        fun(Session) ->
222                Cookie = yaws_session_server:cookie(Session),
223                delete(Cookie)
224        end,
225    lists:foreach(Cleaner, list()).
226
227traverse(Gnow) ->
228    Timeouter =
229        fun (Session) ->
230                case yaws_session_server:has_timedout(Session, Gnow) of
231                    false ->
232                        ok;
233                    true ->
234                        yaws_session_server:report_timedout_sess(Session),
235                        Cookie = yaws_session_server:cookie(Session),
236                        delete(Cookie)
237                end
238        end,
239    lists:foreach(Timeouter, list()).
240
241stop_backend() ->
242    ?MODULE ! stop,
243    ok.
244
245%% Mock Internals
246mock_session_server() ->
247    register(?MODULE, self()),
248    proc_lib:init_ack({ok, self()}),
249    mock_loop().
250
251mock_loop() ->
252    receive
253        {list, Pid} ->
254            Pid ! {ok, sessions()},
255            mock_loop();
256        {insert, Session} ->
257            Cookie = element(2, Session),
258            put({session, Cookie}, Session),
259            mock_loop();
260        {{lookup, Cookie}, Pid} ->
261            Session = get({session, Cookie}),
262            Pid ! {ok, Session},
263            mock_loop();
264        {delete, Cookie} ->
265            erase({session, Cookie}),
266            mock_loop();
267        stop ->
268            ok;
269        Other ->
270            ?debugFmt("Unexpected: ~p~n", [Other])
271    end.
272
273sessions() ->
274    lists:foldl(fun({{session, _}, Session}, Acc) -> [Session | Acc];
275                    (_, Acc) -> Acc
276                 end, [], get()).
277
278%%====================================================================
279
280-define(SPECIAL_COOKIE, "my special cookie").
281new_cookie() ->
282    ?SPECIAL_COOKIE.
283
284new_cookiegen_session(_Config) ->
285    Cookie1 = yaws_session_server:new_session({opaque, 1}),
286    Cookie2 = yaws_session_server:new_session({opaque, 2}, 1, self()),
287    Cookie3 = yaws_session_server:new_session({opaque, 3}, 1, self(), "cookie"),
288    ?assertEqual(?SPECIAL_COOKIE, Cookie1),
289    ?assertEqual(Cookie1, Cookie2),
290    ?assertEqual("cookie", Cookie3),
291    ok.
292