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    case misc:is_ipv6() of
55        false ->
56            put(addr, couch_config:get("httpd", "ip4_bind_address", "127.0.0.1"));
57        true ->
58            IP6Addr = couch_config:get("httpd", "ip6_bind_address", "::1"),
59            put(addr, "[" ++ IP6Addr ++ "]")
60    end,
61    put(port, integer_to_list(mochiweb_socket_server:get(couch_httpd, port))),
62
63    create_new_doc(),
64    query_view_before_restore_backup(),
65
66    % restore DB file backup after querying view
67    restore_backup_db_file(),
68
69    query_view_after_restore_backup(),
70
71    delete_db(),
72    couch_server_sup:stop(),
73    ok.
74
75admin_user_ctx() ->
76    {user_ctx, #user_ctx{roles=[<<"_admin">>]}}.
77
78create_db() ->
79    {ok, _} = couch_db:create(test_db_name(), [admin_user_ctx()]).
80
81delete_db() ->
82    couch_server:delete(test_db_name(), [admin_user_ctx()]).
83
84create_docs() ->
85    {ok, Db} = couch_db:open(test_db_name(), [admin_user_ctx()]),
86    Doc1 = couch_doc:from_json_obj({[
87        {<<"meta">>, {[
88            {<<"id">>, <<"doc1">>}
89        ]}},
90        {<<"json">>, {[
91            {<<"value">>, 1}
92        ]}}
93    ]}),
94    Doc2 = couch_doc:from_json_obj({[
95        {<<"meta">>, {[
96            {<<"id">>, <<"doc2">>}
97        ]}},
98        {<<"json">>, {[
99            {<<"value">>, 2}
100        ]}}
101    ]}),
102    Doc3 = couch_doc:from_json_obj({[
103        {<<"meta">>, {[
104            {<<"id">>, <<"doc3">>}
105        ]}},
106        {<<"json">>, {[
107            {<<"value">>, 3}
108        ]}}
109    ]}),
110    ok = couch_db:update_docs(Db, [Doc1, Doc2, Doc3]),
111    couch_db:ensure_full_commit(Db),
112    couch_db:close(Db).
113
114create_design_doc() ->
115    {ok, Db} = couch_db:open(test_db_name(), [admin_user_ctx()]),
116    DDoc = couch_doc:from_json_obj({[
117        {<<"meta">>, {[
118            {<<"id">>, <<"_design/foo">>}
119        ]}},
120        {<<"json">>, {[
121            {<<"language">>, <<"javascript">>},
122            {<<"views">>, {[
123                {<<"bar">>, {[
124                    {<<"map">>, <<"function(doc) { emit(doc.value, 1); }">>}
125                ]}}
126            ]}}
127        ]}}
128    ]}),
129    ok = couch_db:update_docs(Db, [DDoc]),
130    couch_db:ensure_full_commit(Db),
131    couch_db:close(Db).
132
133backup_db_file() ->
134    DbFile = test_util:build_file("tmp/lib/" ++
135        binary_to_list(test_db_name()) ++ ".couch.1"),
136    {ok, _} = file:copy(DbFile, DbFile ++ ".backup"),
137    ok.
138
139create_new_doc() ->
140    {ok, Db} = couch_db:open(test_db_name(), [admin_user_ctx()]),
141    Doc666 = couch_doc:from_json_obj({[
142        {<<"meta">>, {[
143            {<<"id">>, <<"doc666">>}
144        ]}},
145        {<<"json">>, {[
146            {<<"value">>, 999}
147        ]}}
148    ]}),
149    ok = couch_db:update_docs(Db, [Doc666]),
150    couch_db:ensure_full_commit(Db),
151    couch_db:close(Db).
152
153db_url() ->
154    "http://" ++ get(addr) ++ ":" ++ get(port) ++ "/" ++
155    binary_to_list(test_db_name()).
156
157query_view_before_restore_backup() ->
158    {ok, Code, _Headers, Body} = test_util:request(
159        db_url() ++ "/_design/foo/_view/bar", [], get),
160    etap:is(Code, 200, "Got view response before restoring backup."),
161    ViewJson = ejson:decode(Body),
162    Rows = couch_util:get_nested_json_value(ViewJson, [<<"rows">>]),
163    HasDoc1 = has_doc("doc1", Rows),
164    HasDoc2 = has_doc("doc2", Rows),
165    HasDoc3 = has_doc("doc3", Rows),
166    HasDoc666 = has_doc("doc666", Rows),
167    etap:is(HasDoc1, true, "Before backup restore, view has doc1"),
168    etap:is(HasDoc2, true, "Before backup restore, view has doc2"),
169    etap:is(HasDoc3, true, "Before backup restore, view has doc3"),
170    etap:is(HasDoc666, true, "Before backup restore, view has doc666"),
171    ok.
172
173has_doc(DocId1, Rows) ->
174    DocId = iolist_to_binary(DocId1),
175    lists:any(
176        fun({R}) -> lists:member({<<"id">>, DocId}, R) end,
177        Rows
178    ).
179
180restore_backup_db_file() ->
181    couch_server_sup:stop(),
182    timer:sleep(3000),
183    DbFile = test_util:build_file("tmp/lib/" ++
184        binary_to_list(test_db_name()) ++ ".couch.1"),
185    % NOTE vmx 2016-01-25: overwrite the original file via renaming. This
186    % works well on Windows (as opposed to deleting the file first).
187    ok = file:rename(DbFile ++ ".backup", DbFile),
188    couch_server_sup:start_link(test_util:config_files()),
189    timer:sleep(1000),
190    put(port, integer_to_list(mochiweb_socket_server:get(couch_httpd, port))),
191    ok.
192
193query_view_after_restore_backup() ->
194    {ok, Code, _Headers, Body} = test_util:request(
195        db_url() ++ "/_design/foo/_view/bar", [], get),
196    etap:is(Code, 200, "Got view response after restoring backup."),
197    ViewJson = ejson:decode(Body),
198    Rows = couch_util:get_nested_json_value(ViewJson, [<<"rows">>]),
199    HasDoc1 = has_doc("doc1", Rows),
200    HasDoc2 = has_doc("doc2", Rows),
201    HasDoc3 = has_doc("doc3", Rows),
202    HasDoc666 = has_doc("doc666", Rows),
203    etap:is(HasDoc1, true, "After backup restore, view has doc1"),
204    etap:is(HasDoc2, true, "After backup restore, view has doc2"),
205    etap:is(HasDoc3, true, "After backup restore, view has doc3"),
206    etap:is(HasDoc666, false, "After backup restore, view does not have doc666"),
207    ok.
208