1*b42e852eSBaptiste Daroussin#- 2*b42e852eSBaptiste Daroussin# SPDX-License-Identifier: BSD-2-Clause 3*b42e852eSBaptiste Daroussin# 4*b42e852eSBaptiste Daroussin# Copyright (c) 2026 Baptiste Daroussin <bapt@FreeBSD.org> 5*b42e852eSBaptiste Daroussin 6*b42e852eSBaptiste DaroussinPKG_SERVE="${PKG_SERVE:-/usr/libexec/pkg-serve}" 7*b42e852eSBaptiste Daroussin 8*b42e852eSBaptiste Daroussinserve() 9*b42e852eSBaptiste Daroussin{ 10*b42e852eSBaptiste Daroussin printf "$1" | "${PKG_SERVE}" "$2" 11*b42e852eSBaptiste Daroussin} 12*b42e852eSBaptiste Daroussin 13*b42e852eSBaptiste Daroussincheck_output() 14*b42e852eSBaptiste Daroussin{ 15*b42e852eSBaptiste Daroussin local pattern="$1" ; shift 16*b42e852eSBaptiste Daroussin output=$(serve "$@") 17*b42e852eSBaptiste Daroussin case "$output" in 18*b42e852eSBaptiste Daroussin *${pattern}*) 19*b42e852eSBaptiste Daroussin return 0 20*b42e852eSBaptiste Daroussin ;; 21*b42e852eSBaptiste Daroussin *) 22*b42e852eSBaptiste Daroussin echo "Expected pattern: ${pattern}" 23*b42e852eSBaptiste Daroussin echo "Got: ${output}" 24*b42e852eSBaptiste Daroussin return 1 25*b42e852eSBaptiste Daroussin ;; 26*b42e852eSBaptiste Daroussin esac 27*b42e852eSBaptiste Daroussin} 28*b42e852eSBaptiste Daroussin 29*b42e852eSBaptiste Daroussinatf_test_case greeting 30*b42e852eSBaptiste Daroussingreeting_head() 31*b42e852eSBaptiste Daroussin{ 32*b42e852eSBaptiste Daroussin atf_set "descr" "Server sends greeting on connect" 33*b42e852eSBaptiste Daroussin} 34*b42e852eSBaptiste Daroussingreeting_body() 35*b42e852eSBaptiste Daroussin{ 36*b42e852eSBaptiste Daroussin mkdir repo 37*b42e852eSBaptiste Daroussin check_output "ok: pkg-serve " "quit\n" repo || 38*b42e852eSBaptiste Daroussin atf_fail "greeting not found" 39*b42e852eSBaptiste Daroussin} 40*b42e852eSBaptiste Daroussin 41*b42e852eSBaptiste Daroussinatf_test_case unknown_command 42*b42e852eSBaptiste Daroussinunknown_command_head() 43*b42e852eSBaptiste Daroussin{ 44*b42e852eSBaptiste Daroussin atf_set "descr" "Unknown commands get ko response" 45*b42e852eSBaptiste Daroussin} 46*b42e852eSBaptiste Daroussinunknown_command_body() 47*b42e852eSBaptiste Daroussin{ 48*b42e852eSBaptiste Daroussin mkdir repo 49*b42e852eSBaptiste Daroussin check_output "ko: unknown command 'plop'" "plop\nquit\n" repo || 50*b42e852eSBaptiste Daroussin atf_fail "expected ko for unknown command" 51*b42e852eSBaptiste Daroussin} 52*b42e852eSBaptiste Daroussin 53*b42e852eSBaptiste Daroussinatf_test_case get_missing_file 54*b42e852eSBaptiste Daroussinget_missing_file_head() 55*b42e852eSBaptiste Daroussin{ 56*b42e852eSBaptiste Daroussin atf_set "descr" "Requesting a missing file returns ko" 57*b42e852eSBaptiste Daroussin} 58*b42e852eSBaptiste Daroussinget_missing_file_body() 59*b42e852eSBaptiste Daroussin{ 60*b42e852eSBaptiste Daroussin mkdir repo 61*b42e852eSBaptiste Daroussin check_output "ko: file not found" "get nonexistent.pkg 0\nquit\n" repo || 62*b42e852eSBaptiste Daroussin atf_fail "expected file not found" 63*b42e852eSBaptiste Daroussin} 64*b42e852eSBaptiste Daroussin 65*b42e852eSBaptiste Daroussinatf_test_case get_file 66*b42e852eSBaptiste Daroussinget_file_head() 67*b42e852eSBaptiste Daroussin{ 68*b42e852eSBaptiste Daroussin atf_set "descr" "Requesting an existing file returns its content" 69*b42e852eSBaptiste Daroussin} 70*b42e852eSBaptiste Daroussinget_file_body() 71*b42e852eSBaptiste Daroussin{ 72*b42e852eSBaptiste Daroussin mkdir repo 73*b42e852eSBaptiste Daroussin echo "testcontent" > repo/test.pkg 74*b42e852eSBaptiste Daroussin output=$(serve "get test.pkg 0\nquit\n" repo) 75*b42e852eSBaptiste Daroussin echo "$output" | grep -q "ok: 12" || 76*b42e852eSBaptiste Daroussin atf_fail "expected ok: 12, got: ${output}" 77*b42e852eSBaptiste Daroussin echo "$output" | grep -q "testcontent" || 78*b42e852eSBaptiste Daroussin atf_fail "expected testcontent in output" 79*b42e852eSBaptiste Daroussin} 80*b42e852eSBaptiste Daroussin 81*b42e852eSBaptiste Daroussinatf_test_case get_file_leading_slash 82*b42e852eSBaptiste Daroussinget_file_leading_slash_head() 83*b42e852eSBaptiste Daroussin{ 84*b42e852eSBaptiste Daroussin atf_set "descr" "Leading slash in path is stripped" 85*b42e852eSBaptiste Daroussin} 86*b42e852eSBaptiste Daroussinget_file_leading_slash_body() 87*b42e852eSBaptiste Daroussin{ 88*b42e852eSBaptiste Daroussin mkdir repo 89*b42e852eSBaptiste Daroussin echo "testcontent" > repo/test.pkg 90*b42e852eSBaptiste Daroussin check_output "ok: 12" "get /test.pkg 0\nquit\n" repo || 91*b42e852eSBaptiste Daroussin atf_fail "leading slash not stripped" 92*b42e852eSBaptiste Daroussin} 93*b42e852eSBaptiste Daroussin 94*b42e852eSBaptiste Daroussinatf_test_case get_file_uptodate 95*b42e852eSBaptiste Daroussinget_file_uptodate_head() 96*b42e852eSBaptiste Daroussin{ 97*b42e852eSBaptiste Daroussin atf_set "descr" "File with old mtime returns ok: 0" 98*b42e852eSBaptiste Daroussin} 99*b42e852eSBaptiste Daroussinget_file_uptodate_body() 100*b42e852eSBaptiste Daroussin{ 101*b42e852eSBaptiste Daroussin mkdir repo 102*b42e852eSBaptiste Daroussin echo "testcontent" > repo/test.pkg 103*b42e852eSBaptiste Daroussin check_output "ok: 0" "get test.pkg 9999999999\nquit\n" repo || 104*b42e852eSBaptiste Daroussin atf_fail "expected ok: 0 for up-to-date file" 105*b42e852eSBaptiste Daroussin} 106*b42e852eSBaptiste Daroussin 107*b42e852eSBaptiste Daroussinatf_test_case get_directory 108*b42e852eSBaptiste Daroussinget_directory_head() 109*b42e852eSBaptiste Daroussin{ 110*b42e852eSBaptiste Daroussin atf_set "descr" "Requesting a directory returns ko" 111*b42e852eSBaptiste Daroussin} 112*b42e852eSBaptiste Daroussinget_directory_body() 113*b42e852eSBaptiste Daroussin{ 114*b42e852eSBaptiste Daroussin mkdir -p repo/subdir 115*b42e852eSBaptiste Daroussin check_output "ko: not a file" "get subdir 0\nquit\n" repo || 116*b42e852eSBaptiste Daroussin atf_fail "expected not a file" 117*b42e852eSBaptiste Daroussin} 118*b42e852eSBaptiste Daroussin 119*b42e852eSBaptiste Daroussinatf_test_case get_missing_age 120*b42e852eSBaptiste Daroussinget_missing_age_head() 121*b42e852eSBaptiste Daroussin{ 122*b42e852eSBaptiste Daroussin atf_set "descr" "get without age argument returns error" 123*b42e852eSBaptiste Daroussin} 124*b42e852eSBaptiste Daroussinget_missing_age_body() 125*b42e852eSBaptiste Daroussin{ 126*b42e852eSBaptiste Daroussin mkdir repo 127*b42e852eSBaptiste Daroussin check_output "ko: bad command get" "get test.pkg\nquit\n" repo || 128*b42e852eSBaptiste Daroussin atf_fail "expected bad command get" 129*b42e852eSBaptiste Daroussin} 130*b42e852eSBaptiste Daroussin 131*b42e852eSBaptiste Daroussinatf_test_case get_bad_age 132*b42e852eSBaptiste Daroussinget_bad_age_head() 133*b42e852eSBaptiste Daroussin{ 134*b42e852eSBaptiste Daroussin atf_set "descr" "get with non-numeric age returns error" 135*b42e852eSBaptiste Daroussin} 136*b42e852eSBaptiste Daroussinget_bad_age_body() 137*b42e852eSBaptiste Daroussin{ 138*b42e852eSBaptiste Daroussin mkdir repo 139*b42e852eSBaptiste Daroussin check_output "ko: bad number" "get test.pkg notanumber\nquit\n" repo || 140*b42e852eSBaptiste Daroussin atf_fail "expected bad number" 141*b42e852eSBaptiste Daroussin} 142*b42e852eSBaptiste Daroussin 143*b42e852eSBaptiste Daroussinatf_test_case get_empty_arg 144*b42e852eSBaptiste Daroussinget_empty_arg_head() 145*b42e852eSBaptiste Daroussin{ 146*b42e852eSBaptiste Daroussin atf_set "descr" "get with no arguments returns error" 147*b42e852eSBaptiste Daroussin} 148*b42e852eSBaptiste Daroussinget_empty_arg_body() 149*b42e852eSBaptiste Daroussin{ 150*b42e852eSBaptiste Daroussin mkdir repo 151*b42e852eSBaptiste Daroussin check_output "ko: bad command get" "get \nquit\n" repo || 152*b42e852eSBaptiste Daroussin atf_fail "expected bad command get" 153*b42e852eSBaptiste Daroussin} 154*b42e852eSBaptiste Daroussin 155*b42e852eSBaptiste Daroussinatf_test_case path_traversal 156*b42e852eSBaptiste Daroussinpath_traversal_head() 157*b42e852eSBaptiste Daroussin{ 158*b42e852eSBaptiste Daroussin atf_set "descr" "Path traversal with .. is rejected" 159*b42e852eSBaptiste Daroussin} 160*b42e852eSBaptiste Daroussinpath_traversal_body() 161*b42e852eSBaptiste Daroussin{ 162*b42e852eSBaptiste Daroussin mkdir repo 163*b42e852eSBaptiste Daroussin check_output "ko: file not found" \ 164*b42e852eSBaptiste Daroussin "get ../etc/passwd 0\nquit\n" repo || 165*b42e852eSBaptiste Daroussin atf_fail "path traversal not rejected" 166*b42e852eSBaptiste Daroussin} 167*b42e852eSBaptiste Daroussin 168*b42e852eSBaptiste Daroussinatf_test_case get_subdir_file 169*b42e852eSBaptiste Daroussinget_subdir_file_head() 170*b42e852eSBaptiste Daroussin{ 171*b42e852eSBaptiste Daroussin atf_set "descr" "Files in subdirectories are served" 172*b42e852eSBaptiste Daroussin} 173*b42e852eSBaptiste Daroussinget_subdir_file_body() 174*b42e852eSBaptiste Daroussin{ 175*b42e852eSBaptiste Daroussin mkdir -p repo/sub 176*b42e852eSBaptiste Daroussin echo "subcontent" > repo/sub/file.pkg 177*b42e852eSBaptiste Daroussin output=$(serve "get sub/file.pkg 0\nquit\n" repo) 178*b42e852eSBaptiste Daroussin echo "$output" | grep -q "ok: 11" || 179*b42e852eSBaptiste Daroussin atf_fail "expected ok: 11, got: ${output}" 180*b42e852eSBaptiste Daroussin echo "$output" | grep -q "subcontent" || 181*b42e852eSBaptiste Daroussin atf_fail "expected subcontent in output" 182*b42e852eSBaptiste Daroussin} 183*b42e852eSBaptiste Daroussin 184*b42e852eSBaptiste Daroussinatf_test_case multiple_gets 185*b42e852eSBaptiste Daroussinmultiple_gets_head() 186*b42e852eSBaptiste Daroussin{ 187*b42e852eSBaptiste Daroussin atf_set "descr" "Multiple get commands in one session" 188*b42e852eSBaptiste Daroussin} 189*b42e852eSBaptiste Daroussinmultiple_gets_body() 190*b42e852eSBaptiste Daroussin{ 191*b42e852eSBaptiste Daroussin mkdir repo 192*b42e852eSBaptiste Daroussin echo "aaa" > repo/a.pkg 193*b42e852eSBaptiste Daroussin echo "bbb" > repo/b.pkg 194*b42e852eSBaptiste Daroussin output=$(serve "get a.pkg 0\nget b.pkg 0\nquit\n" repo) 195*b42e852eSBaptiste Daroussin echo "$output" | grep -q "ok: 4" || 196*b42e852eSBaptiste Daroussin atf_fail "expected ok: 4 for a.pkg" 197*b42e852eSBaptiste Daroussin echo "$output" | grep -q "aaa" || 198*b42e852eSBaptiste Daroussin atf_fail "expected content of a.pkg" 199*b42e852eSBaptiste Daroussin echo "$output" | grep -q "bbb" || 200*b42e852eSBaptiste Daroussin atf_fail "expected content of b.pkg" 201*b42e852eSBaptiste Daroussin} 202*b42e852eSBaptiste Daroussin 203*b42e852eSBaptiste Daroussinatf_test_case bad_basedir 204*b42e852eSBaptiste Daroussinbad_basedir_head() 205*b42e852eSBaptiste Daroussin{ 206*b42e852eSBaptiste Daroussin atf_set "descr" "Non-existent basedir causes exit failure" 207*b42e852eSBaptiste Daroussin} 208*b42e852eSBaptiste Daroussinbad_basedir_body() 209*b42e852eSBaptiste Daroussin{ 210*b42e852eSBaptiste Daroussin atf_check -s not-exit:0 -e match:"open" \ 211*b42e852eSBaptiste Daroussin "${PKG_SERVE}" /nonexistent/path 212*b42e852eSBaptiste Daroussin} 213*b42e852eSBaptiste Daroussin 214*b42e852eSBaptiste Daroussinatf_init_test_cases() 215*b42e852eSBaptiste Daroussin{ 216*b42e852eSBaptiste Daroussin atf_add_test_case greeting 217*b42e852eSBaptiste Daroussin atf_add_test_case unknown_command 218*b42e852eSBaptiste Daroussin atf_add_test_case get_missing_file 219*b42e852eSBaptiste Daroussin atf_add_test_case get_file 220*b42e852eSBaptiste Daroussin atf_add_test_case get_file_leading_slash 221*b42e852eSBaptiste Daroussin atf_add_test_case get_file_uptodate 222*b42e852eSBaptiste Daroussin atf_add_test_case get_directory 223*b42e852eSBaptiste Daroussin atf_add_test_case get_missing_age 224*b42e852eSBaptiste Daroussin atf_add_test_case get_bad_age 225*b42e852eSBaptiste Daroussin atf_add_test_case get_empty_arg 226*b42e852eSBaptiste Daroussin atf_add_test_case path_traversal 227*b42e852eSBaptiste Daroussin atf_add_test_case get_subdir_file 228*b42e852eSBaptiste Daroussin atf_add_test_case multiple_gets 229*b42e852eSBaptiste Daroussin atf_add_test_case bad_basedir 230*b42e852eSBaptiste Daroussin} 231