1#!/usr/bin/env escript
2%% -*- erlang -*-
3%%! -smp enable
4
5% Licensed under the Apache License, Version 2.0 (the "License"); you may not
6% use this file except in compliance with the License. You may obtain a copy of
7% the License at
8%
9%   http://www.apache.org/licenses/LICENSE-2.0
10%
11% Unless required by applicable law or agreed to in writing, software
12% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14% License for the specific language governing permissions and limitations under
15% the License.
16
17-record(user_ctx, {
18    name = null,
19    roles = [],
20    handler
21}).
22
23test_db_name() ->
24    <<"couch_test_invalid_view_seq">>.
25
26main(_) ->
27    test_util:init_code_path(),
28
29    etap:plan(10),
30    case (catch test()) of
31        ok ->
32            etap:end_tests();
33        Other ->
34            etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
35            etap:bail(Other)
36    end,
37    ok.
38
39%% NOTE: since during the test we stop the server,
40%%       a huge and ugly but harmless stack trace is sent to stderr
41%%
42test() ->
43    couch_server_sup:start_link(test_util:config_files()),
44    timer:sleep(1000),
45    delete_db(),
46    create_db(),
47
48    create_docs(),
49    create_design_doc(),
50
51    % make DB file backup
52    backup_db_file(),
53
54    put(addr, couch_config:get("httpd", "bind_address", "127.0.0.1")),
55    put(port, integer_to_list(mochiweb_socket_server:get(couch_httpd, port))),
56
57    create_new_doc(),
58    query_view_before_restore_backup(),
59
60    % restore DB file backup after querying view
61    restore_backup_db_file(),
62
63    query_view_after_restore_backup(),
64
65    delete_db(),
66    couch_server_sup:stop(),
67    ok.
68
69admin_user_ctx() ->
70    {user_ctx, #user_ctx{roles=[<<"_admin">>]}}.
71
72create_db() ->
73    {ok, _} = couch_db:create(test_db_name(), [admin_user_ctx()]).
74
75delete_db() ->
76    couch_server:delete(test_db_name(), [admin_user_ctx()]).
77
78create_docs() ->
79    {ok, Db} = couch_db:open(test_db_name(), [admin_user_ctx()]),
80    Doc1 = couch_doc:from_json_obj({[
81        {<<"meta">>, {[
82            {<<"id">>, <<"doc1">>}
83        ]}},
84        {<<"json">>, {[
85            {<<"value">>, 1}
86        ]}}
87    ]}),
88    Doc2 = couch_doc:from_json_obj({[
89        {<<"meta">>, {[
90            {<<"id">>, <<"doc2">>}
91        ]}},
92        {<<"json">>, {[
93            {<<"value">>, 2}
94        ]}}
95    ]}),
96    Doc3 = couch_doc:from_json_obj({[
97        {<<"meta">>, {[
98            {<<"id">>, <<"doc3">>}
99        ]}},
100        {<<"json">>, {[
101            {<<"value">>, 3}
102        ]}}
103    ]}),
104    ok = couch_db:update_docs(Db, [Doc1, Doc2, Doc3]),
105    couch_db:ensure_full_commit(Db),
106    couch_db:close(Db).
107
108create_design_doc() ->
109    {ok, Db} = couch_db:open(test_db_name(), [admin_user_ctx()]),
110    DDoc = couch_doc:from_json_obj({[
111        {<<"meta">>, {[
112            {<<"id">>, <<"_design/foo">>}
113        ]}},
114        {<<"json">>, {[
115            {<<"language">>, <<"javascript">>},
116            {<<"views">>, {[
117                {<<"bar">>, {[
118                    {<<"map">>, <<"function(doc) { emit(doc.value, 1); }">>}
119                ]}}
120            ]}}
121        ]}}
122    ]}),
123    ok = couch_db:update_docs(Db, [DDoc]),
124    couch_db:ensure_full_commit(Db),
125    couch_db:close(Db).
126
127backup_db_file() ->
128    DbFile = test_util:build_file("tmp/lib/" ++
129        binary_to_list(test_db_name()) ++ ".couch.1"),
130    {ok, _} = file:copy(DbFile, DbFile ++ ".backup"),
131    ok.
132
133create_new_doc() ->
134    {ok, Db} = couch_db:open(test_db_name(), [admin_user_ctx()]),
135    Doc666 = couch_doc:from_json_obj({[
136        {<<"meta">>, {[
137            {<<"id">>, <<"doc666">>}
138        ]}},
139        {<<"json">>, {[
140            {<<"value">>, 999}
141        ]}}
142    ]}),
143    ok = couch_db:update_docs(Db, [Doc666]),
144    couch_db:ensure_full_commit(Db),
145    couch_db:close(Db).
146
147db_url() ->
148    "http://" ++ get(addr) ++ ":" ++ get(port) ++ "/" ++
149    binary_to_list(test_db_name()).
150
151query_view_before_restore_backup() ->
152    {ok, Code, _Headers, Body} = test_util:request(
153        db_url() ++ "/_design/foo/_view/bar", [], get),
154    etap:is(Code, 200, "Got view response before restoring backup."),
155    ViewJson = ejson:decode(Body),
156    Rows = couch_util:get_nested_json_value(ViewJson, [<<"rows">>]),
157    HasDoc1 = has_doc("doc1", Rows),
158    HasDoc2 = has_doc("doc2", Rows),
159    HasDoc3 = has_doc("doc3", Rows),
160    HasDoc666 = has_doc("doc666", Rows),
161    etap:is(HasDoc1, true, "Before backup restore, view has doc1"),
162    etap:is(HasDoc2, true, "Before backup restore, view has doc2"),
163    etap:is(HasDoc3, true, "Before backup restore, view has doc3"),
164    etap:is(HasDoc666, true, "Before backup restore, view has doc666"),
165    ok.
166
167has_doc(DocId1, Rows) ->
168    DocId = iolist_to_binary(DocId1),
169    lists:any(
170        fun({R}) -> lists:member({<<"id">>, DocId}, R) end,
171        Rows
172    ).
173
174restore_backup_db_file() ->
175    couch_server_sup:stop(),
176    timer:sleep(3000),
177    DbFile = test_util:build_file("tmp/lib/" ++
178        binary_to_list(test_db_name()) ++ ".couch.1"),
179    % NOTE vmx 2016-01-25: overwrite the original file via renaming. This
180    % works well on Windows (as opposed to deleting the file first).
181    ok = file:rename(DbFile ++ ".backup", DbFile),
182    couch_server_sup:start_link(test_util:config_files()),
183    timer:sleep(1000),
184    put(port, integer_to_list(mochiweb_socket_server:get(couch_httpd, port))),
185    ok.
186
187query_view_after_restore_backup() ->
188    {ok, Code, _Headers, Body} = test_util:request(
189        db_url() ++ "/_design/foo/_view/bar", [], get),
190    etap:is(Code, 200, "Got view response after restoring backup."),
191    ViewJson = ejson:decode(Body),
192    Rows = couch_util:get_nested_json_value(ViewJson, [<<"rows">>]),
193    HasDoc1 = has_doc("doc1", Rows),
194    HasDoc2 = has_doc("doc2", Rows),
195    HasDoc3 = has_doc("doc3", Rows),
196    HasDoc666 = has_doc("doc666", Rows),
197    etap:is(HasDoc1, true, "After backup restore, view has doc1"),
198    etap:is(HasDoc2, true, "After backup restore, view has doc2"),
199    etap:is(HasDoc3, true, "After backup restore, view has doc3"),
200    etap:is(HasDoc666, false, "After backup restore, view does not have doc666"),
201    ok.
202