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
4 changes: 1 addition & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,10 @@ jobs:
rebar3 xref
rebar3 as test eunit

# Optional integration tests with real CouchDB
# End-to-end tests against a real CouchDB
integration:
name: Integration Tests (CouchDB)
runs-on: ubuntu-22.04
# This job is optional - don't block CI if it fails
continue-on-error: true

services:
couchdb:
Expand Down
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@ All notable changes to this project will be documented in this file.

### Compatibility

- Supports OTP 27, 28 and 29.
- CI now also runs on OTP 29.
- Requires OTP 27+. Dropped the OTP-version crypto shim and now always use
`crypto:mac/4`.
- CI runs on OTP 27, 28 and 29.

### Testing

- End-to-end tests run the full client against a real CouchDB. `make e2e`
starts CouchDB in Docker, runs the suite, and tears it down. CI runs all
suite groups and they now gate the build.

### Dependencies

Expand Down
3 changes: 1 addition & 2 deletions rebar.config
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
%%-*- mode: erlang -*-


{erl_opts, [debug_info,
{platform_define, "^(2[3-9])", 'USE_CRYPTO_MAC'}]}.
{erl_opts, [debug_info]}.

%% oauth is an optional dependency - ignore xref warnings for it
{xref_ignores, [{oauth, header_params_encode, 1}, {oauth, sign, 6}]}.
Expand Down
6 changes: 1 addition & 5 deletions src/couchbeam_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -285,10 +285,6 @@ hgv(N,L) ->
proxy_token(Secret,UserName) ->
hackney_bstr:to_hex(hmac(sha, Secret, UserName)).

-ifdef(USE_CRYPTO_MAC).
%% couchbeam requires OTP 27+, where crypto:mac/4 is always available.
hmac(Alg, Key, Data) ->
crypto:mac(hmac, Alg, Key, Data).
-else.
hmac(Alg, Key, Data) ->
crypto:hmac(Alg, Key, Data).
-endif.
58 changes: 56 additions & 2 deletions test/couchbeam_integration_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
-export([doc_with_attachment/1,
multiple_attachments/1,
large_attachment/1,
attachment_streaming/1]).
attachment_streaming/1,
doc_multipart_stream/1]).

%% Test cases - Views
-export([all_docs/1,
Expand Down Expand Up @@ -162,7 +163,8 @@ groups() ->
doc_with_attachment,
multiple_attachments,
large_attachment,
attachment_streaming
attachment_streaming,
doc_multipart_stream
]},
{view_ops, [sequence], [
all_docs,
Expand Down Expand Up @@ -807,6 +809,58 @@ collect_stream(Ref, Acc) ->
ct:fail("Stream error: ~p", [Reason])
end.

%% Test reading a document and its attachments as a multipart stream.
doc_multipart_stream(Config) ->
Db = ?config(db, Config),

%% Create a document with two attachments
Doc = #{<<"_id">> => <<"mp_stream">>, <<"type">> => <<"test">>},
{ok, DocSaved} = couchbeam:save_doc(Db, Doc),
Rev0 = maps:get(<<"_rev">>, DocSaved),
%% Use a non-compressible content type so CouchDB returns the bytes
%% verbatim in the multipart response (text/* attachments are gzipped).
{ok, Att1} = couchbeam:put_attachment(Db, <<"mp_stream">>, <<"a.bin">>,
<<"alpha">>,
[{rev, Rev0},
{content_type, <<"application/octet-stream">>}]),
Rev1 = maps:get(<<"rev">>, Att1),
{ok, _} = couchbeam:put_attachment(Db, <<"mp_stream">>, <<"b.bin">>,
<<"bravo">>,
[{rev, Rev1},
{content_type, <<"application/octet-stream">>}]),

%% Open it as a multipart stream (attachment bodies included)
{ok, {multipart, State}} = couchbeam:open_doc(Db, <<"mp_stream">>,
[{"attachments", true}]),

{DocOut, AttsOut} = collect_multipart(State, undefined, <<>>, #{}),

<<"mp_stream">> = maps:get(<<"_id">>, DocOut),
true = maps:is_key(<<"_attachments">>, DocOut),
<<"alpha">> = maps:get(<<"a.bin">>, AttsOut),
<<"bravo">> = maps:get(<<"b.bin">>, AttsOut),

ct:pal("Multipart doc stream test passed"),
ok.

%% Drive couchbeam:stream_doc/1 to completion, collecting the document and
%% each attachment's bytes.
collect_multipart(State, Doc, CurBuf, Atts) ->
case couchbeam:stream_doc(State) of
{doc, NewDoc, NState} ->
collect_multipart(NState, NewDoc, CurBuf, Atts);
{att, _Name, NState} ->
collect_multipart(NState, Doc, <<>>, Atts);
{att_body, _Name, Chunk, NState} ->
collect_multipart(NState, Doc, <<CurBuf/binary, Chunk/binary>>, Atts);
{att_eof, Name, NState} ->
collect_multipart(NState, Doc, <<>>, Atts#{Name => CurBuf});
eof ->
{Doc, Atts};
{error, Reason} ->
ct:fail("Multipart stream error: ~p", [Reason])
end.

%%====================================================================
%% View tests
%%====================================================================
Expand Down
Loading