feat(dispatch): implement dispatch-workflow.sh with dispatch, poll, and timeout (#4)
Add dispatch-workflow.sh script that dispatches a Gitea workflow in another repository and polls synchronously for completion. Refactor mock-api.sh to use Python3 HTTP server with sequence support, enabling stateful poll-response simulation in tests. --------- Co-authored-by: moilanik <niko.moilanen@tietoevry.com> Reviewed-on: #4
This commit is contained in:
+60
-14
@@ -6,12 +6,14 @@ MOCK_PID=""
|
||||
MOCK_REQUEST_FILE=""
|
||||
MOCK_RESPONSE_CODE=201
|
||||
MOCK_STATE_FILE="/tmp/mock_api_state"
|
||||
MOCK_SEQUENCE_FILE=""
|
||||
MOCK_CONFIG_FILE=""
|
||||
|
||||
_kill_port() {
|
||||
local pids
|
||||
pids=$(lsof -ti ":$MOCK_PORT" 2>/dev/null) || true
|
||||
[ -n "$pids" ] && kill $pids 2>/dev/null || true
|
||||
sleep 0.2
|
||||
[ -n "$pids" ] && kill -9 $pids 2>/dev/null || true
|
||||
sleep 0.3
|
||||
}
|
||||
|
||||
_wait_port_free() {
|
||||
@@ -22,32 +24,52 @@ _wait_port_free() {
|
||||
done
|
||||
}
|
||||
|
||||
mock_set_sequence() {
|
||||
MOCK_SEQUENCE_FILE=$(mktemp)
|
||||
echo "$1" | jq -c '.' > "$MOCK_SEQUENCE_FILE"
|
||||
echo "0" > "${MOCK_SEQUENCE_FILE}.idx"
|
||||
}
|
||||
|
||||
mock_clear_sequence() {
|
||||
MOCK_SEQUENCE_FILE=""
|
||||
}
|
||||
|
||||
mock_start() {
|
||||
MOCK_RESPONSE_CODE="${MOCK_RESPONSE_CODE:-201}"
|
||||
MOCK_REQUEST_FILE=$(mktemp)
|
||||
echo "$MOCK_REQUEST_FILE" > "$MOCK_STATE_FILE"
|
||||
MOCK_CONFIG_FILE=$(mktemp)
|
||||
|
||||
if [ -n "${MOCK_SEQUENCE_FILE:-}" ] && [ -f "$MOCK_SEQUENCE_FILE" ]; then
|
||||
echo "SEQUENCE" > "$MOCK_CONFIG_FILE"
|
||||
cat "$MOCK_SEQUENCE_FILE" >> "$MOCK_CONFIG_FILE"
|
||||
printf '%s' "${MOCK_SEQUENCE_FILE}.idx" >> "$MOCK_CONFIG_FILE"
|
||||
else
|
||||
echo "SINGLE" > "$MOCK_CONFIG_FILE"
|
||||
echo "$MOCK_RESPONSE_CODE" >> "$MOCK_CONFIG_FILE"
|
||||
fi
|
||||
|
||||
_kill_port
|
||||
_wait_port_free
|
||||
|
||||
(
|
||||
while true; do
|
||||
printf 'HTTP/1.1 %d OK\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n{"id":1}\n' "$MOCK_RESPONSE_CODE" \
|
||||
| nc -l "$MOCK_PORT" >> "$MOCK_REQUEST_FILE" 2>/dev/null || break
|
||||
sleep 0.05
|
||||
done
|
||||
) &
|
||||
nohup python3 "$(dirname "${BASH_SOURCE[0]}")/mock-server.py" "$MOCK_PORT" "$MOCK_CONFIG_FILE" "$MOCK_REQUEST_FILE" \
|
||||
</dev/null >/dev/null 2>&1 &
|
||||
MOCK_PID=$!
|
||||
sleep 0.3
|
||||
sleep 0.5
|
||||
}
|
||||
|
||||
mock_stop() {
|
||||
[ -n "${MOCK_PID:-}" ] && kill "$MOCK_PID" 2>/dev/null || true
|
||||
[ -n "${MOCK_PID:-}" ] && kill -9 "$MOCK_PID" 2>/dev/null || true
|
||||
_kill_port
|
||||
_wait_port_free
|
||||
[ -n "${MOCK_REQUEST_FILE:-}" ] && rm -f "${MOCK_REQUEST_FILE}" 2>/dev/null || true
|
||||
[ -n "${MOCK_SEQUENCE_FILE:-}" ] && rm -f "${MOCK_SEQUENCE_FILE}" 2>/dev/null || true
|
||||
[ -n "${MOCK_SEQUENCE_FILE:-}" ] && rm -f "${MOCK_SEQUENCE_FILE}.idx" 2>/dev/null || true
|
||||
[ -n "${MOCK_CONFIG_FILE:-}" ] && rm -f "${MOCK_CONFIG_FILE}" 2>/dev/null || true
|
||||
rm -f "$MOCK_STATE_FILE"
|
||||
MOCK_PID=""
|
||||
MOCK_SEQUENCE_FILE=""
|
||||
MOCK_CONFIG_FILE=""
|
||||
}
|
||||
|
||||
mock_set_response() {
|
||||
@@ -65,13 +87,37 @@ _get_request_file() {
|
||||
}
|
||||
|
||||
mock_get_request_body() {
|
||||
tail -1 "$(_get_request_file)" 2>/dev/null || true
|
||||
local rf
|
||||
rf=$(_get_request_file)
|
||||
[ -f "$rf" ] && tail -1 "$rf" 2>/dev/null || true
|
||||
}
|
||||
|
||||
mock_get_request_path() {
|
||||
head -1 "$(_get_request_file)" 2>/dev/null | awk '{print $2}' || true
|
||||
local rf
|
||||
rf=$(_get_request_file)
|
||||
[ -f "$rf" ] && tail -2 "$rf" 2>/dev/null | head -1 | awk '{print $2}' || true
|
||||
}
|
||||
|
||||
mock_get_first_request_path() {
|
||||
local rf
|
||||
rf=$(_get_request_file)
|
||||
[ -f "$rf" ] && head -1 "$rf" 2>/dev/null | awk '{print $2}' || true
|
||||
}
|
||||
|
||||
mock_get_first_request_method() {
|
||||
local rf
|
||||
rf=$(_get_request_file)
|
||||
[ -f "$rf" ] && head -1 "$rf" 2>/dev/null | awk '{print $1}' || true
|
||||
}
|
||||
|
||||
mock_get_first_request_body() {
|
||||
local rf
|
||||
rf=$(_get_request_file)
|
||||
[ -f "$rf" ] && sed -n '2p' "$rf" 2>/dev/null || true
|
||||
}
|
||||
|
||||
mock_get_request_method() {
|
||||
head -1 "$(_get_request_file)" 2>/dev/null | awk '{print $1}' || true
|
||||
local rf
|
||||
rf=$(_get_request_file)
|
||||
[ -f "$rf" ] && tail -2 "$rf" 2>/dev/null | head -1 | awk '{print $1}' || true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
import http.server, json, sys, os, threading
|
||||
|
||||
PORT = int(sys.argv[1])
|
||||
CONFIG = sys.argv[2]
|
||||
REQ_FILE = sys.argv[3]
|
||||
|
||||
idx_lock = threading.Lock()
|
||||
mode = 'SINGLE'
|
||||
default_code = 200
|
||||
responses = []
|
||||
idx_path = ''
|
||||
|
||||
with open(CONFIG) as f:
|
||||
mode = f.readline().strip()
|
||||
if mode == 'SEQUENCE':
|
||||
seq_raw = f.readline().strip()
|
||||
idx_path = f.readline().strip()
|
||||
responses = json.loads(seq_raw)
|
||||
else:
|
||||
default_code = int(f.readline().strip())
|
||||
|
||||
def read_idx():
|
||||
try:
|
||||
with open(idx_path) as f2:
|
||||
return int(f2.read().strip())
|
||||
except:
|
||||
return 0
|
||||
|
||||
def write_idx(v):
|
||||
with open(idx_path, 'w') as f2:
|
||||
f2.write(str(v))
|
||||
|
||||
class H(http.server.BaseHTTPRequestHandler):
|
||||
def _get_response(self):
|
||||
if mode == 'SEQUENCE':
|
||||
with idx_lock:
|
||||
i = read_idx()
|
||||
r = responses[min(i, len(responses)-1)]
|
||||
write_idx(i + 1)
|
||||
code = r.get('code', 200)
|
||||
body = r.get('body', {'id':1})
|
||||
return code, json.dumps(body)
|
||||
return default_code, json.dumps({'id':1})
|
||||
|
||||
def _log_request(self, method):
|
||||
path = self.path
|
||||
content_len = int(self.headers.get('Content-Length', 0))
|
||||
body = self.rfile.read(content_len).decode() if content_len else ''
|
||||
line = f'{method} {path}\n{body}\n'
|
||||
with open(REQ_FILE, 'a') as f:
|
||||
f.write(line)
|
||||
|
||||
def do_GET(self):
|
||||
self._log_request('GET')
|
||||
code, body = self._get_response()
|
||||
self.send_response(code)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(body.encode())
|
||||
|
||||
def do_POST(self):
|
||||
self._log_request('POST')
|
||||
code, body = self._get_response()
|
||||
self.send_response(code)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(body.encode())
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
s = http.server.HTTPServer(('', PORT), H)
|
||||
s.allow_reuse_address = True
|
||||
s.serve_forever()
|
||||
Reference in New Issue
Block a user