1% Licensed under the Apache License, Version 2.0 (the "License"); you may not
2% use this file except in compliance with the License. You may obtain a copy of
3% the License at
4%
5%   http://www.apache.org/licenses/LICENSE-2.0
6%
7% Unless required by applicable law or agreed to in writing, software
8% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10% License for the specific language governing permissions and limitations under
11% the License.
12
13-module(cpse_test_purge_replication).
14-compile(export_all).
15-compile(nowarn_export_all).
16
17
18-include_lib("eunit/include/eunit.hrl").
19-include_lib("couch/include/couch_db.hrl").
20-include_lib("mem3/include/mem3.hrl").
21
22
23setup_all() ->
24    cpse_util:setup_all([mem3, fabric, couch_replicator]).
25
26
27setup_each() ->
28    {ok, Src} = cpse_util:create_db(),
29    {ok, Tgt} = cpse_util:create_db(),
30    {couch_db:name(Src), couch_db:name(Tgt)}.
31
32
33teardown_each({SrcDb, TgtDb}) ->
34    ok = couch_server:delete(SrcDb, []),
35    ok = couch_server:delete(TgtDb, []).
36
37
38cpse_purge_http_replication({Source, Target}) ->
39    {ok, Rev1} = cpse_util:save_doc(Source, {[{'_id', foo}, {vsn, 1}]}),
40
41    cpse_util:assert_db_props(?MODULE, ?LINE, Source, [
42        {doc_count, 1},
43        {del_doc_count, 0},
44        {update_seq, 1},
45        {changes, 1},
46        {purge_seq, 0},
47        {purge_infos, []}
48    ]),
49
50    RepObject = {[
51        {<<"source">>, db_url(Source)},
52        {<<"target">>, db_url(Target)}
53    ]},
54
55    {ok, _} = couch_replicator:replicate(RepObject, ?ADMIN_USER),
56    {ok, Doc1} = cpse_util:open_doc(Target, foo),
57
58    cpse_util:assert_db_props(?MODULE, ?LINE, Target, [
59        {doc_count, 1},
60        {del_doc_count, 0},
61        {update_seq, 1},
62        {changes, 1},
63        {purge_seq, 0},
64        {purge_infos, []}
65    ]),
66
67    PurgeInfos = [
68        {cpse_util:uuid(), <<"foo">>, [Rev1]}
69    ],
70
71    {ok, [{ok, PRevs}]} = cpse_util:purge(Source, PurgeInfos),
72    ?assertEqual([Rev1], PRevs),
73
74    cpse_util:assert_db_props(?MODULE, ?LINE, Source, [
75        {doc_count, 0},
76        {del_doc_count, 0},
77        {update_seq, 2},
78        {changes, 0},
79        {purge_seq, 1},
80        {purge_infos, PurgeInfos}
81    ]),
82
83    % Show that a purge on the source is
84    % not replicated to the target
85    {ok, _} = couch_replicator:replicate(RepObject, ?ADMIN_USER),
86    {ok, Doc2} = cpse_util:open_doc(Target, foo),
87    [Rev2] = Doc2#doc_info.revs,
88    ?assertEqual(Rev1, Rev2#rev_info.rev),
89    ?assertEqual(Doc1, Doc2),
90
91    cpse_util:assert_db_props(?MODULE, ?LINE, Target, [
92        {doc_count, 1},
93        {del_doc_count, 0},
94        {update_seq, 1},
95        {changes, 1},
96        {purge_seq, 0},
97        {purge_infos, []}
98    ]),
99
100    % Show that replicating from the target
101    % back to the source reintroduces the doc
102    RepObject2 = {[
103        {<<"source">>, db_url(Target)},
104        {<<"target">>, db_url(Source)}
105    ]},
106
107    {ok, _} = couch_replicator:replicate(RepObject2, ?ADMIN_USER),
108    {ok, Doc3} = cpse_util:open_doc(Source, foo),
109    [Revs3] = Doc3#doc_info.revs,
110    ?assertEqual(Rev1, Revs3#rev_info.rev),
111
112    cpse_util:assert_db_props(?MODULE, ?LINE, Source, [
113        {doc_count, 1},
114        {del_doc_count, 0},
115        {update_seq, 3},
116        {changes, 1},
117        {purge_seq, 1},
118        {purge_infos, PurgeInfos}
119    ]).
120
121
122cpse_purge_internal_repl_disabled({Source, Target}) ->
123    cpse_util:with_config([{"mem3", "replicate_purges", "false"}], fun() ->
124        repl(Source, Target),
125
126        {ok, [Rev1, Rev2]} = cpse_util:save_docs(Source, [
127            {[{'_id', foo1}, {vsn, 1}]},
128            {[{'_id', foo2}, {vsn, 2}]}
129        ]),
130
131        repl(Source, Target),
132
133        PurgeInfos1 = [
134            {cpse_util:uuid(), <<"foo1">>, [Rev1]}
135        ],
136        {ok, [{ok, PRevs1}]} = cpse_util:purge(Source, PurgeInfos1),
137        ?assertEqual([Rev1], PRevs1),
138
139        PurgeInfos2 = [
140            {cpse_util:uuid(), <<"foo2">>, [Rev2]}
141        ],
142        {ok, [{ok, PRevs2}]} = cpse_util:purge(Target, PurgeInfos2),
143        ?assertEqual([Rev2], PRevs2),
144
145        SrcShard = make_shard(Source),
146        TgtShard = make_shard(Target),
147        ?assertEqual({ok, 0}, mem3_rep:go(SrcShard, TgtShard)),
148        ?assertEqual({ok, 0}, mem3_rep:go(TgtShard, SrcShard)),
149
150        ?assertMatch({ok, #doc_info{}}, cpse_util:open_doc(Source, <<"foo2">>)),
151        ?assertMatch({ok, #doc_info{}}, cpse_util:open_doc(Target, <<"foo1">>))
152    end).
153
154
155cpse_purge_repl_simple_pull({Source, Target}) ->
156    repl(Source, Target),
157
158    {ok, Rev} = cpse_util:save_doc(Source, {[{'_id', foo}, {vsn, 1}]}),
159    repl(Source, Target),
160
161    PurgeInfos = [
162        {cpse_util:uuid(), <<"foo">>, [Rev]}
163    ],
164    {ok, [{ok, PRevs}]} = cpse_util:purge(Target, PurgeInfos),
165    ?assertEqual([Rev], PRevs),
166    repl(Source, Target).
167
168
169cpse_purge_repl_simple_push({Source, Target}) ->
170    repl(Source, Target),
171
172    {ok, Rev} = cpse_util:save_doc(Source, {[{'_id', foo}, {vsn, 1}]}),
173    repl(Source, Target),
174
175    PurgeInfos = [
176        {cpse_util:uuid(), <<"foo">>, [Rev]}
177    ],
178    {ok, [{ok, PRevs}]} = cpse_util:purge(Source, PurgeInfos),
179    ?assertEqual([Rev], PRevs),
180    repl(Source, Target).
181
182
183repl(Source, Target) ->
184    SrcShard = make_shard(Source),
185    TgtShard = make_shard(Target),
186
187    ?assertEqual({ok, 0}, mem3_rep:go(SrcShard, TgtShard)),
188
189    SrcTerm = cpse_util:db_as_term(Source, replication),
190    TgtTerm = cpse_util:db_as_term(Target, replication),
191
192    Diff = cpse_util:term_diff(SrcTerm, TgtTerm),
193    ?assertEqual(nodiff, Diff).
194
195
196make_shard(DbName) ->
197    #shard{
198        name = DbName,
199        node = node(),
200        dbname = DbName,
201        range = [0, 16#FFFFFFFF]
202    }.
203
204
205db_url(DbName) ->
206    Addr = config:get("httpd", "bind_address", "127.0.0.1"),
207    Port = mochiweb_socket_server:get(couch_httpd, port),
208    Url = ?l2b(io_lib:format("http://~s:~b/~s", [Addr, Port, DbName])),
209    test_util:wait(fun() ->
210        case test_request:get(?b2l(Url)) of
211            {ok, 200, _, _} -> ok;
212            _ -> wait
213        end
214    end),
215    Url.
216