Skip to content

Commit 7aea6d9

Browse files
committed
feat(nginx): scope /members/ redirect to network via /network/{subdomain} path
Requests to /members/ arriving on a network subdomain (e.g. stemedplus.hcommons.org) now redirect to the Profiles app at /network/{subdomain}/members/ so Profiles can filter the member list to that network. Requests on the bare domain keep redirecting to the unscoped /members/ listing. Adds a behavioural test that runs the real nginx config in a container and asserts on redirect Location headers for all four environments.
1 parent d5fbee1 commit 7aea6d9

5 files changed

Lines changed: 190 additions & 4 deletions

File tree

config/dev/nginx/templates/25-buddypress-redirects.conf.template

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
# this has to be ordered after 20-buddypress.conf.template has been loaded
22

3-
# Member profile redirects to new Profiles application
3+
# Member profile redirects to new Profiles application.
4+
# Requests on a network subdomain (captured as $subdomain in
5+
# 00-hcommons.conf.template) carry the network in the redirect path so
6+
# Profiles can scope the member list to that network.
47
location /members/ {
8+
if ($subdomain) {
9+
return 301 https://profile.hcommons-dev.org/network/$subdomain$request_uri;
10+
}
511
return 301 https://profile.hcommons-dev.org$request_uri;
612
}
713

config/production/nginx/templates/25-buddypress-redirects.conf.template

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
# this has to be ordered after 20-buddypress.conf.template has been loaded
22

3-
# Member profile redirects to new Profiles application
3+
# Member profile redirects to new Profiles application.
4+
# Requests on a network subdomain (captured as $subdomain in
5+
# 00-hcommons.conf.template) carry the network in the redirect path so
6+
# Profiles can scope the member list to that network.
47
location /members/ {
8+
if ($subdomain) {
9+
return 301 https://profile.hcommons.org/network/$subdomain$request_uri;
10+
}
511
return 301 https://profile.hcommons.org$request_uri;
612
}
713

config/staging/nginx/templates/25-buddypress-redirects.conf.template

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
# this has to be ordered after 20-buddypress.conf.template has been loaded
22

3-
# Member profile redirects to new Profiles application
3+
# Member profile redirects to new Profiles application.
4+
# Requests on a network subdomain (captured as $subdomain in
5+
# 00-hcommons.conf.template) carry the network in the redirect path so
6+
# Profiles can scope the member list to that network.
47
location /members/ {
8+
if ($subdomain) {
9+
return 301 https://profile.hcommons-staging.org/network/$subdomain$request_uri;
10+
}
511
return 301 https://profile.hcommons-staging.org$request_uri;
612
}
713

config/test/nginx/templates/25-buddypress-redirects.conf.template

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
# this has to be ordered after 20-buddypress.conf.template has been loaded
22

3-
# Member profile redirects to new Profiles application
3+
# Member profile redirects to new Profiles application.
4+
# Requests on a network subdomain (captured as $subdomain in
5+
# 00-hcommons.conf.template) carry the network in the redirect path so
6+
# Profiles can scope the member list to that network.
47
location /members/ {
8+
if ($subdomain) {
9+
return 301 https://profile.hcommons-test.org/network/$subdomain$request_uri;
10+
}
511
return 301 https://profile.hcommons-test.org$request_uri;
612
}
713

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Behavioural test for the /members/ -> Profiles redirects in
4+
# config/*/nginx/templates/25-buddypress-redirects.conf.template.
5+
#
6+
# Runs the real nginx.conf + templates (merged the same way Dockerfile.nginx
7+
# merges them) inside the stock nginx image, then asserts on the Location
8+
# headers nginx returns for various Host/path combinations.
9+
#
10+
# Usage: tests/nginx/test-members-redirects.sh [env ...]
11+
# (defaults to all of: production dev staging test)
12+
13+
set -u
14+
15+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
16+
NGINX_IMAGE="nginx:stable-alpine3.19"
17+
CONTAINER_NAME="kc-nginx-redirect-test"
18+
HOST_PORT=8089
19+
20+
ENVS=("$@")
21+
[ ${#ENVS[@]} -eq 0 ] && ENVS=(production dev staging test)
22+
23+
profile_domain() {
24+
case "$1" in
25+
production) echo "profile.hcommons.org" ;;
26+
dev) echo "profile.hcommons-dev.org" ;;
27+
staging) echo "profile.hcommons-staging.org" ;;
28+
test) echo "profile.hcommons-test.org" ;;
29+
*) echo "unknown-env" ;;
30+
esac
31+
}
32+
33+
site_domain() {
34+
# The DOMAIN_NAME env var each deployment runs with.
35+
case "$1" in
36+
production) echo "hcommons.org" ;;
37+
dev) echo "hcommons-dev.org" ;;
38+
staging) echo "hcommons-staging.org" ;;
39+
test) echo "hcommons-test.org" ;;
40+
*) echo "unknown-env" ;;
41+
esac
42+
}
43+
44+
PASS=0
45+
FAIL=0
46+
47+
cleanup() {
48+
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1
49+
[ -n "${TMPDIR_TEMPLATES:-}" ] && rm -rf "$TMPDIR_TEMPLATES"
50+
}
51+
trap cleanup EXIT
52+
53+
start_nginx() {
54+
local env="$1" domain="$2"
55+
56+
cleanup
57+
TMPDIR_TEMPLATES="$(mktemp -d)"
58+
mkdir -p "$TMPDIR_TEMPLATES/templates"
59+
# Replicate the Dockerfile.nginx overlay: base templates, then env overlay.
60+
cp -a "$REPO_ROOT/config/all/nginx/templates/." "$TMPDIR_TEMPLATES/templates/"
61+
if [ -d "$REPO_ROOT/config/$env/nginx/templates" ]; then
62+
cp -a "$REPO_ROOT/config/$env/nginx/templates/." "$TMPDIR_TEMPLATES/templates/"
63+
fi
64+
65+
docker run -d --name "$CONTAINER_NAME" \
66+
-e "DOMAIN_NAME=$domain" \
67+
--add-host app:127.0.0.1 \
68+
-v "$REPO_ROOT/config/all/nginx/nginx.conf:/etc/nginx/nginx.conf:ro" \
69+
-v "$TMPDIR_TEMPLATES/templates:/etc/nginx/templates:ro" \
70+
-p "127.0.0.1:$HOST_PORT:80" \
71+
"$NGINX_IMAGE" >/dev/null || return 1
72+
73+
# Wait for nginx to answer.
74+
for _ in $(seq 1 30); do
75+
if curl -s -o /dev/null "http://127.0.0.1:$HOST_PORT/" 2>/dev/null; then
76+
return 0
77+
fi
78+
sleep 0.5
79+
done
80+
echo "nginx did not become ready for env=$env" >&2
81+
docker logs "$CONTAINER_NAME" >&2
82+
return 1
83+
}
84+
85+
# assert_redirect <env> <host> <path> <expected-location>
86+
assert_redirect() {
87+
local env="$1" host="$2" path="$3" expected="$4"
88+
local out code location
89+
out="$(curl -s -o /dev/null -w '%{http_code}\t%{redirect_url}' \
90+
-H "Host: $host" "http://127.0.0.1:$HOST_PORT$path")"
91+
code="${out%% *}"
92+
location="${out#* }"
93+
if [ "$code" = "301" ] && [ "$location" = "$expected" ]; then
94+
PASS=$((PASS + 1))
95+
echo "PASS [$env] $host$path -> $location"
96+
else
97+
FAIL=$((FAIL + 1))
98+
echo "FAIL [$env] $host$path"
99+
echo " expected: 301 $expected"
100+
echo " got: $code ${location:-<none>}"
101+
fi
102+
}
103+
104+
# assert_not_redirected_to_profiles <env> <host> <path>
105+
assert_not_redirected_to_profiles() {
106+
local env="$1" host="$2" path="$3"
107+
local out code location
108+
out="$(curl -s -o /dev/null -w '%{http_code}\t%{redirect_url}' \
109+
-H "Host: $host" "http://127.0.0.1:$HOST_PORT$path")"
110+
code="${out%% *}"
111+
location="${out#* }"
112+
case "$location" in
113+
https://profile.*)
114+
FAIL=$((FAIL + 1))
115+
echo "FAIL [$env] $host$path unexpectedly redirected to $location"
116+
;;
117+
*)
118+
PASS=$((PASS + 1))
119+
echo "PASS [$env] $host$path stays in WordPress (status $code)"
120+
;;
121+
esac
122+
}
123+
124+
for env in "${ENVS[@]}"; do
125+
domain="$(site_domain "$env")"
126+
profile="$(profile_domain "$env")"
127+
echo "=== env: $env (DOMAIN_NAME=$domain -> $profile) ==="
128+
129+
start_nginx "$env" "$domain" || { FAIL=$((FAIL + 1)); continue; }
130+
131+
# Network subdomain: members directory is scoped to the network.
132+
assert_redirect "$env" "stemedplus.$domain" "/members/" \
133+
"https://$profile/network/stemedplus/members/"
134+
135+
# Deeper member path keeps the full request URI after the network prefix.
136+
assert_redirect "$env" "stemedplus.$domain" "/members/somebody/" \
137+
"https://$profile/network/stemedplus/members/somebody/"
138+
139+
# Query strings survive the redirect.
140+
assert_redirect "$env" "stemedplus.$domain" "/members/?page=2" \
141+
"https://$profile/network/stemedplus/members/?page=2"
142+
143+
# A site within a network still resolves to the network.
144+
assert_redirect "$env" "asite.stemedplus.$domain" "/members/" \
145+
"https://$profile/network/stemedplus/members/"
146+
147+
# Bare domain (no subdomain): unscoped members directory.
148+
assert_redirect "$env" "$domain" "/members/" \
149+
"https://$profile/members/"
150+
151+
# Excluded BuddyPress member sub-pages stay in WordPress.
152+
assert_not_redirected_to_profiles "$env" "stemedplus.$domain" \
153+
"/members/somebody/activity/"
154+
155+
# Registration redirect is unchanged by the network scoping.
156+
assert_redirect "$env" "stemedplus.$domain" "/membership/" \
157+
"https://$profile/registration/start/"
158+
done
159+
160+
echo
161+
echo "passed: $PASS failed: $FAIL"
162+
[ "$FAIL" -eq 0 ]

0 commit comments

Comments
 (0)