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
10 changes: 1 addition & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,4 @@ jobs:
COUCHDB_URL: http://localhost:5984
COUCHDB_USER: admin
COUCHDB_PASS: admin
run: |
# Run each test group individually for better isolation
for group in server_ops database_ops document_ops bulk_ops attachment_ops \
view_streaming_ops changes_streaming_ops replication_ops \
db_management_ops mango_ops uuid_ops; do
echo "=== Running $group ==="
rebar3 ct --suite=couchbeam_integration_SUITE --group=$group --readable=true || exit 1
done
echo "All integration tests passed"
run: ./support/run-e2e.sh
54 changes: 54 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# couchbeam developer tasks.

COUCHDB_URL ?= http://127.0.0.1:5984
COUCHDB_USER ?= admin
COUCHDB_PASS ?= admin
COMPOSE ?= docker compose

export COUCHDB_URL COUCHDB_USER COUCHDB_PASS

.PHONY: all compile eunit xref dialyzer test e2e e2e-up e2e-run e2e-down clean

all: compile

compile:
rebar3 compile

eunit:
rebar3 eunit

xref:
rebar3 xref

dialyzer:
rebar3 dialyzer

test: eunit xref

## Run the end-to-end suite against a throwaway CouchDB (start, run, stop).
e2e: e2e-up
@./support/run-e2e.sh; status=$$?; $(COMPOSE) down -v; exit $$status

## Start CouchDB and create the system databases.
e2e-up:
$(COMPOSE) up -d
@echo "Waiting for CouchDB at $(COUCHDB_URL)..."
@for i in $$(seq 1 60); do \
curl -sf $(COUCHDB_URL)/_up >/dev/null 2>&1 && break; \
sleep 1; \
done
@for db in _users _replicator _global_changes; do \
curl -s -u $(COUCHDB_USER):$(COUCHDB_PASS) -X PUT $(COUCHDB_URL)/$$db >/dev/null || true; \
done
@echo "CouchDB ready at $(COUCHDB_URL)"

## Run the e2e suite against an already-running CouchDB.
e2e-run:
./support/run-e2e.sh

## Stop and remove the CouchDB container and its data.
e2e-down:
$(COMPOSE) down -v

clean:
rebar3 clean
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,31 @@ ok = couchbeam:delete_attachment(Db, Doc, "file.txt").
| `couchbeam_changes` | Changes feed |
| `couchbeam_attachments` | Inline attachment helpers |

## Testing

Unit tests run without any external services:

```
rebar3 eunit
```

End-to-end tests run the full client against a real CouchDB. With Docker
available, one command starts CouchDB, runs the suite, and tears it down:

```
make e2e
```

To use a CouchDB you already run, point the runner at it:

```
COUCHDB_URL=http://127.0.0.1:5984 COUCHDB_USER=admin COUCHDB_PASS=admin \
./support/run-e2e.sh
```

`make e2e-up` / `make e2e-down` start and stop the bundled CouchDB
(`docker-compose.yml`) on their own.

## Contributing

Found a bug or have a feature request? [Open an issue](https://github.com/benoitc/couchbeam/issues).
Expand Down
15 changes: 15 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Local CouchDB used by the couchbeam end-to-end tests.
# See `make e2e` (or support/run-e2e.sh) for the test runner.
services:
couchdb:
image: couchdb:3.3
environment:
COUCHDB_USER: admin
COUCHDB_PASSWORD: admin
ports:
- "5984:5984"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5984/_up"]
interval: 5s
timeout: 5s
retries: 20
29 changes: 29 additions & 0 deletions support/run-e2e.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env sh
#
# Run the couchbeam end-to-end suite against a real CouchDB.
#
# Each test group runs as a separate `rebar3 ct` invocation so every group
# starts from a fresh suite setup (its own database), which keeps them
# isolated. Honors:
# COUCHDB_URL (default http://127.0.0.1:5984)
# COUCHDB_USER (default empty, i.e. no auth)
# COUCHDB_PASS (default empty)
#
set -eu

COUCHDB_URL="${COUCHDB_URL:-http://127.0.0.1:5984}"
COUCHDB_USER="${COUCHDB_USER:-}"
COUCHDB_PASS="${COUCHDB_PASS:-}"
export COUCHDB_URL COUCHDB_USER COUCHDB_PASS

e2e_groups="server_ops database_ops document_ops bulk_ops attachment_ops \
view_ops design_ops changes_ops error_handling \
view_streaming_ops changes_streaming_ops replication_ops \
db_management_ops mango_ops uuid_ops"

for group in $e2e_groups; do
echo "=== e2e group: ${group} ==="
rebar3 ct --suite=couchbeam_integration_SUITE --group="${group}" --readable=true
done

echo "All e2e groups passed"
41 changes: 21 additions & 20 deletions test/couchbeam_integration_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -822,7 +822,7 @@ all_docs(Config) ->
{ok, _} = couchbeam:save_docs(Db, Docs),

%% Query all_docs
{ok, AllDocs} = couchbeam_view:all(Db, [{include_docs, true}]),
{ok, AllDocs} = couchbeam_view:all(Db, [include_docs]),
true = length(AllDocs) >= 10,

%% Verify structure
Expand Down Expand Up @@ -985,9 +985,9 @@ view_reduce(Config) ->
view_count(Config) ->
Db = ?config(db, Config),

%% Count all documents
{ok, Count} = couchbeam_view:count(Db),
true = Count > 0,
%% Count all documents (count/1 returns a bare integer)
Count = couchbeam_view:count(Db),
true = is_integer(Count) andalso Count > 0,
ct:pal("Document count: ~p", [Count]),

ok.
Expand All @@ -997,7 +997,7 @@ view_first(Config) ->
Db = ?config(db, Config),

%% Get first document
{ok, First} = couchbeam_view:first(Db, [{include_docs, true}]),
{ok, First} = couchbeam_view:first(Db, 'all_docs', [include_docs]),
true = is_map(First),
true = maps:is_key(<<"id">>, First),
ct:pal("First document: ~p", [maps:get(<<"id">>, First)]),
Expand All @@ -1008,19 +1008,21 @@ view_first(Config) ->
view_fold(Config) ->
Db = ?config(db, Config),

%% Fold over documents counting them
{ok, FoldCount} = couchbeam_view:fold(Db, fun(_Row, Acc) ->
{ok, Acc + 1}
end, 0, []),
%% Fold over documents counting them. fold/5 is
%% fold(Function, Acc, Db, ViewName, Options); the function returns the
%% next accumulator (or stop) and fold returns the final accumulator.
FoldCount = couchbeam_view:fold(fun(_Row, Acc) ->
Acc + 1
end, 0, Db, 'all_docs', []),

true = FoldCount > 0,
true = is_integer(FoldCount) andalso FoldCount > 0,
ct:pal("Fold counted ~p documents", [FoldCount]),

%% Fold collecting IDs
{ok, Ids} = couchbeam_view:fold(Db, fun(Row, Acc) ->
Ids = couchbeam_view:fold(fun(Row, Acc) ->
Id = maps:get(<<"id">>, Row),
{ok, [Id | Acc]}
end, [], [{limit, 5}]),
[Id | Acc]
end, [], Db, 'all_docs', [{limit, 5}]),
5 = length(Ids),

ok.
Expand Down Expand Up @@ -1057,11 +1059,10 @@ design_info(Config) ->
view_cleanup(Config) ->
Db = ?config(db, Config),

%% Trigger view cleanup
{ok, Result} = couchbeam:view_cleanup(Db),
true = maps:get(<<"ok">>, Result, false),
%% Trigger view cleanup (returns ok)
ok = couchbeam:view_cleanup(Db),

ct:pal("View cleanup result: ~p", [Result]),
ct:pal("View cleanup triggered"),
ok.

%%====================================================================
Expand Down Expand Up @@ -1202,11 +1203,11 @@ error_conflict(Config) ->
error_invalid_doc(Config) ->
Server = ?config(server, Config),

%% Try to open non-existent database
{error, not_found} = couchbeam:open_db(Server, <<"this_db_does_not_exist_xyz">>),
%% open_db is a local operation; a missing database only surfaces on use
{ok, MissingDb} = couchbeam:open_db(Server, <<"this_db_does_not_exist_xyz">>),
{error, db_not_found} = couchbeam:db_info(MissingDb),

%% Try to create database with invalid name (empty)
%% Note: CouchDB may accept some unusual names, so we test with clearly invalid chars
{error, _} = couchbeam:create_db(Server, <<"">>),

ct:pal("Invalid document/database errors handled correctly"),
Expand Down
Loading