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
6 changes: 6 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copilot Instructions

Repository-specific agent instructions are defined in [AGENTS.md](../AGENTS.md).

- Read and follow [AGENTS.md](../AGENTS.md) before making changes.
- Use [AGENTS.md](../AGENTS.md) as the source of truth for this repository.
21 changes: 21 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# AGENTS.md

This file is the primary instruction source for coding agents working in this repository.

## Scope

- Follow this file before using other repo-specific agent instruction files.
- Treat this repository as a Docker-based integration-test harness for the fmsg stack.

## Integration Test Guidance

- The integration tests live under `test/` and exercise the system with `fmsg-cli`.
- For CLI behavior, flags, and command syntax used by the integration tests, use the fmsg-cli README as the authoritative reference:
https://github.com/markmnl/fmsg-cli/blob/main/README.md
- Do not assume `CLI_USAGE.md` in this repository is the canonical CLI reference if it conflicts with the upstream fmsg-cli README.

## Editing Guidance

- Keep changes minimal and targeted.
- Preserve the existing shell style in test scripts and runner scripts.
- When adding or updating integration tests, follow the existing numbering and naming pattern in `test/tests/`.
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# CLAUDE.md

Repository-specific agent instructions are defined in [AGENTS.md](AGENTS.md).

- Read and follow [AGENTS.md](AGENTS.md) before making changes.
- Use [AGENTS.md](AGENTS.md) as the source of truth if guidance appears in multiple places.
83 changes: 0 additions & 83 deletions CLI_USAGE.md

This file was deleted.

1 change: 1 addition & 0 deletions docker/postgres/init/002-fmsgd-dd.sql
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,4 @@ drop trigger if exists trg_msg_add_to_insert on msg_add_to;
create trigger trg_msg_add_to_insert
after insert on msg_add_to
for each row execute function notify_msg_to_insert();

Empty file modified test/run-tests.sh
100644 → 100755
Empty file.
88 changes: 88 additions & 0 deletions test/test-lib.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env bash

fail_test() {
echo " FAIL: $1"
exit 1
}

extract_send_id() {
echo "$1" | sed -n 's/^ID: \([0-9][0-9]*\)$/\1/p' | head -1
}

extract_wait_latest_id() {
echo "$1" | sed -n 's/^New message available\. Latest ID: \([0-9][0-9]*\)$/\1/p' | head -1
}

get_max_message_id() {
local list_output
local ids

list_output=$(fmsg list 2>/dev/null || true)
ids=$(echo "$list_output" | sed -n 's/^ID: \([0-9][0-9]*\).*/\1/p')

if [ -z "$ids" ]; then
echo 0
return
fi

echo "$ids" | sort -nr | head -1
}

wait_for_new_message_id() {
local since_id="$1"
local timeout="${2:-15}"
local wait_output
local latest_id

wait_output=$(fmsg wait --since-id "$since_id" --timeout "$timeout")
echo " $wait_output" >&2

latest_id=$(extract_wait_latest_id "$wait_output")
if [ -z "$latest_id" ]; then
fail_test "timed out waiting for a new message after ID $since_id"
fi

echo "$latest_id"
}

wait_for_message_id_by_data() {
local expected_data="$1"
local timeout="${2:-15}"
local tmp_file
local attempt
local ids
local id

tmp_file=$(mktemp)

for attempt in $(seq 1 "$timeout"); do
ids=$(fmsg list --limit 20 2>/dev/null | sed -n 's/^ID: \([0-9][0-9]*\).*/\1/p')

for id in $ids; do
if fmsg get-data "$id" "$tmp_file" >/dev/null 2>&1 && grep -Fxq "$expected_data" "$tmp_file"; then
rm -f "$tmp_file"
echo "$id"
return
fi
done

sleep 1
done

rm -f "$tmp_file"
fail_test "timed out waiting for expected message data"
}

get_auth_token() {
local auth_file
local token

auth_file="${XDG_CONFIG_HOME:-$HOME/.config}/fmsg/auth.json"
token=$(sed -n 's/^[[:space:]]*"token":[[:space:]]*"\([^"]*\)".*/\1/p' "$auth_file" | head -1)

if [ -z "$token" ]; then
fail_test "could not determine auth token from $auth_file"
fi

echo "$token"
}
40 changes: 27 additions & 13 deletions test/tests/001-send-message.sh
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
#!/usr/bin/env bash
# Test: Send a message from @alice@hairpin.local to @bob@example.com
# and verify it is received.
# and verify the recipient can download the message data.
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=../test-lib.sh
source "$SCRIPT_DIR/../test-lib.sh"

TMP_DIR=$(mktemp -d)
TEST_TOKEN="$(date +%s)-$$"
MESSAGE_TEXT="Hello Bob, this is an integration test. [$TEST_TOKEN]"
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT

echo " Sending message: @alice@hairpin.local → @bob@example.com"
export FMSG_API_URL="$HAIRPIN_API_URL"
printf '@alice@hairpin.local\n' | fmsg login
sleep 3
fmsg send '@bob@example.com' "Hello Bob, this is an integration test."
fmsg login '@alice@hairpin.local'
SEND_OUTPUT=$(fmsg send '@bob@example.com' "$MESSAGE_TEXT")
echo "$SEND_OUTPUT"

echo " Waiting for cross-instance delivery..."
sleep 5

echo " Reading messages as @bob@example.com"
export FMSG_API_URL="$EXAMPLE_API_URL"
printf '@bob@example.com\n' | fmsg login
sleep 3
MSG_OUTPUT=$(fmsg list)
echo "$MSG_OUTPUT"
fmsg login '@bob@example.com'
MSG_ID=$(wait_for_message_id_by_data "$MESSAGE_TEXT")
echo " Using received message ID: $MSG_ID"

echo " Downloading message data as @bob@example.com"
fmsg get-data "$MSG_ID" "$TMP_DIR/message.txt"

if echo "$MSG_OUTPUT" | grep -q "No messages"; then
echo " FAIL: @bob@example.com has no messages — delivery did not succeed"
echo " Verifying downloaded message data"
if ! grep -Fxq "$MESSAGE_TEXT" "$TMP_DIR/message.txt"; then
fail_test "downloaded message data did not match expected content"
echo " Downloaded data:"
cat "$TMP_DIR/message.txt"
exit 1
fi
30 changes: 18 additions & 12 deletions test/tests/002-reply-message.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,29 @@
# using the parent message ID (pid 1 on a clean database).
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=../test-lib.sh
source "$SCRIPT_DIR/../test-lib.sh"

TEST_TOKEN="$(date +%s)-$$"
MESSAGE_TEXT="Hey there Alice, got your message! [$TEST_TOKEN]"

echo " Sending reply: @bob@example.com → @alice@hairpin.local (pid 1)"
export FMSG_API_URL="$EXAMPLE_API_URL"
printf '@bob@example.com\n' | fmsg login
sleep 3
fmsg send --pid 1 '@alice@hairpin.local' "Hey there Alice, got your message!"
fmsg login '@bob@example.com'
SEND_OUTPUT=$(fmsg send --pid 1 '@alice@hairpin.local' "$MESSAGE_TEXT")
echo "$SEND_OUTPUT"

echo " Waiting for cross-instance delivery..."
sleep 5

echo " Reading messages as @alice@hairpin.local"
export FMSG_API_URL="$HAIRPIN_API_URL"
printf '@alice@hairpin.local\n' | fmsg login
sleep 3
MSG_OUTPUT=$(fmsg list)
fmsg login '@alice@hairpin.local'
MSG_ID=$(wait_for_message_id_by_data "$MESSAGE_TEXT")
echo " Using received message ID: $MSG_ID"

echo " Reading received message as @alice@hairpin.local"
MSG_OUTPUT=$(fmsg get "$MSG_ID")
echo "$MSG_OUTPUT"

if echo "$MSG_OUTPUT" | grep -q "No messages"; then
echo " FAIL: @alice@hairpin.local has no messages — reply delivery did not succeed"
exit 1
if ! echo "$MSG_OUTPUT" | grep -q '^From: @bob@example.com$'; then
fail_test "received message $MSG_ID was not from @bob@example.com"
fi
3 changes: 1 addition & 2 deletions test/tests/003-reply-invalid-pid.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ set -euo pipefail

echo " Sending reply with invalid pid 99: @bob@example.com → @alice@hairpin.local"
export FMSG_API_URL="$EXAMPLE_API_URL"
printf '@bob@example.com\n' | fmsg login
sleep 3
fmsg login '@bob@example.com'

if fmsg send --pid 99 '@alice@hairpin.local' "This should fail" 2>/dev/null; then
echo " FAIL: send with invalid pid 99 succeeded but should have failed"
Expand Down
48 changes: 29 additions & 19 deletions test/tests/004-add-to-recipient.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,47 @@
# and verify carol receives the message.
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=../test-lib.sh
source "$SCRIPT_DIR/../test-lib.sh"

TEST_TOKEN="$(date +%s)-$$"
MESSAGE_TEXT="Hello Bob, this is the add-to integration test. [$TEST_TOKEN]"

echo " Sending initial message: @alice@hairpin.local → @bob@example.com"
export FMSG_API_URL="$HAIRPIN_API_URL"
printf '@alice@hairpin.local\n' | fmsg login
sleep 3
fmsg send '@bob@example.com' "Hello Bob, this is the add-to integration test."
fmsg login '@alice@hairpin.local'
SEND_OUTPUT=$(fmsg send '@bob@example.com' "$MESSAGE_TEXT")
echo "$SEND_OUTPUT"

echo " Waiting for delivery..."
sleep 3

echo " Getting message ID from alice's sent messages"
MSG_ID=$(fmsg list | grep -oE '\b[0-9]+\b' | head -1)
echo " Getting message ID from send output"
MSG_ID=$(extract_send_id "$SEND_OUTPUT")
if [ -z "$MSG_ID" ]; then
echo " FAIL: could not determine message ID from fmsg list"
exit 1
fail_test "could not determine message ID from fmsg send output"
fi
echo " Using message ID: $MSG_ID"

echo " Waiting for bob to receive the original message"
export FMSG_API_URL="$EXAMPLE_API_URL"
fmsg login '@bob@example.com'
ORIGINAL_MSG_ID=$(wait_for_message_id_by_data "$MESSAGE_TEXT")
echo " Bob received original message ID: $ORIGINAL_MSG_ID"

echo " Adding @carol@example.com as recipient via add-to $MSG_ID"
export FMSG_API_URL="$HAIRPIN_API_URL"
fmsg login '@alice@hairpin.local'
fmsg add-to "$MSG_ID" '@carol@example.com'

echo " Waiting for cross-instance delivery..."
sleep 3

echo " Reading messages as @carol@example.com"
export FMSG_API_URL="$EXAMPLE_API_URL"
printf '@carol@example.com\n' | fmsg login
sleep 3
MSG_OUTPUT=$(fmsg list)
fmsg login '@carol@example.com'
RECEIVED_MSG_ID=$(wait_for_message_id_by_data "$MESSAGE_TEXT")
echo " Using received message ID: $RECEIVED_MSG_ID"

echo " Reading received message as @carol@example.com"
MSG_OUTPUT=$(fmsg get "$RECEIVED_MSG_ID")
echo "$MSG_OUTPUT"

if echo "$MSG_OUTPUT" | grep -q "No messages"; then
echo " FAIL: @carol@example.com has no messages — add-to delivery did not succeed"
exit 1
if ! echo "$MSG_OUTPUT" | grep -q '^From: @alice@hairpin.local$'; then
fail_test "received message $RECEIVED_MSG_ID was not from @alice@hairpin.local"
fi
Loading
Loading