Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
setup_file() {
cd "$BATS_TEST_DIRNAME"
npm init --yes
echo '{"name":"exercises","private":true}' > package.json
npm install cheerio crawlee
}

teardown() {
rm -rf products.json storage dataset.json
}

# Like bats' run, but retries up to 3 times if the command fails with HTTP 429
run_retry() {
local attempt
for attempt in 1 2 3; do
run "$@"
if (( status == 0 )) || [[ "$output" != *429* ]]; then
return 0
fi
echo "Attempt $attempt got HTTP 429, retrying..." >&2
sleep $(( attempt * 10 ))
done
}

teardown_file() {
rm -rf node_modules package.json package-lock.json
}
Expand All @@ -27,64 +40,64 @@ teardown_file() {
}

@test "outputs the HTML with Star Wars products" {
run node lego.mjs
run_retry node lego.mjs

[[ "$output" == *"Millennium Falcon"* ]]
}

@test "counts the number of F1 Academy teams" {
run node f1academy_teams.mjs
run_retry node f1academy_teams.mjs

[[ "$output" == "6" ]]
}

@test "counts the number of F1 Academy drivers" {
run node f1academy_drivers.mjs
run_retry node f1academy_drivers.mjs

[[ "$output" == "18" ]]
}

@test "lists IMO countries" {
run node imo_countries.mjs
run_retry node imo_countries.mjs

[[ "$output" == *$'Albania\nLibya\n'* ]]
[[ "$output" == *$'\nZimbabwe\nFaroes\n'* ]]
[[ $(echo "$output" | wc -l) -gt 5 ]]
}

@test "lists IMO countries with a single selector" {
run node imo_countries_single_selector.mjs
run_retry node imo_countries_single_selector.mjs

[[ "$output" == *$'Albania\nLibya\n'* ]]
[[ "$output" == *$'\nZimbabwe\nFaroes\n'* ]]
[[ $(echo "$output" | wc -l) -gt 5 ]]
}

@test "lists Guardian F1 article titles" {
run node guardian_f1_titles.mjs
run_retry node guardian_f1_titles.mjs

[[ "$output" == *' F1 '* ]]
[[ $(echo "$output" | wc -l) -gt 5 ]]
}

@test "prints warehouse stock counts" {
run node warehouse_units.mjs
run_retry node warehouse_units.mjs

[[ "$output" == *$'JBL Flip 4 Waterproof Portable Bluetooth Speaker | 672\n'* ]]
[[ "$output" == *$'Sony XBR-950G BRAVIA 4K HDR Ultra HD TV | 76\n'* ]]
[[ $(echo "$output" | wc -l) -gt 5 ]]
}

@test "prints warehouse stock counts using regex" {
run node warehouse_units_regex.mjs
run_retry node warehouse_units_regex.mjs

[[ "$output" == *$'JBL Flip 4 Waterproof Portable Bluetooth Speaker | 672\n'* ]]
[[ "$output" == *$'Sony XBR-950G BRAVIA 4K HDR Ultra HD TV | 76\n'* ]]
[[ $(echo "$output" | wc -l) -gt 5 ]]
}

@test "prints Guardian F1 titles with publish dates" {
run node guardian_publish_dates.mjs
run_retry node guardian_publish_dates.mjs

[[ "$output" == *' F1 '* ]]
[[ "$output" == *' | Mon '* ]] # has info about date, Mondays are very likely
Expand All @@ -94,27 +107,27 @@ teardown_file() {
@test "filters products from JSON" {
echo '[{"title":"Premium Speakers","minPrice":75000,"price":75000},{"title":"Budget Headphones","minPrice":25000,"price":25000}]' > products.json

run node process_products_json.mjs
run_retry node process_products_json.mjs

[[ "$output" == "{ title: 'Premium Speakers', minPrice: 75000, price: 75000 }" ]]
}

@test "lists WTA player links" {
run node wta_tennis_links.mjs
run_retry node wta_tennis_links.mjs

[[ "$output" == *'https://www.wtatennis.com/players/'* ]]
[[ $(echo "$output" | wc -l) -gt 10 ]]
}

@test "lists Guardian F1 article links" {
run node guardian_f1_links.mjs
run_retry node guardian_f1_links.mjs

[[ "$output" == *'https://www.theguardian.com/sport/'* ]]
[[ $(echo "$output" | wc -l) -gt 5 ]]
}

@test "lists WTA player birthplaces" {
run node wta_tennis_players.mjs
run_retry node wta_tennis_players.mjs

[[ "$output" == *'https://www.wtatennis.com/players/'* ]]
[[ "$output" == *' | '* ]]
Expand All @@ -123,7 +136,7 @@ teardown_file() {
}

@test "lists Guardian F1 authors" {
run node guardian_f1_authors.mjs
run_retry node guardian_f1_authors.mjs

[[ $(echo "$output" | wc -l) -gt 5 ]]
[[ "$output" == *' F1 '* ]]
Expand All @@ -138,7 +151,7 @@ teardown_file() {
}

@test "lists JavaScript GitHub repos with the LLM topic" {
run node js_llm_projects.mjs
run_retry node js_llm_projects.mjs

(( status == 0 ))
[[ $(echo "$output" | wc -l) -eq 37 ]]
Expand All @@ -151,13 +164,13 @@ teardown_file() {
}

@test "counts total eurozone population" {
run node eurozone_population.mjs
run_retry node eurozone_population.mjs

[[ "$output" -gt 300000000 ]]
}

@test "scrapes F1 Academy driver details with Crawlee" {
run node crawlee_f1_drivers.mjs
run_retry node crawlee_f1_drivers.mjs

(( status == 0 ))
[[ -f dataset.json ]]
Expand All @@ -167,7 +180,7 @@ teardown_file() {
}

@test "scrapes Netflix user scores with Crawlee" {
run node crawlee_netflix_ratings.mjs
run_retry node crawlee_netflix_ratings.mjs

(( status == 0 ))
[[ -f dataset.json ]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ teardown() {
rm -rf products.json storage dataset.json
}

# Like bats' run, but retries up to 3 times if the command fails with HTTP 429
run_retry() {
local attempt
for attempt in 1 2 3; do
run "$@"
if (( status == 0 )) || [[ "$output" != *429* ]]; then
return 0
fi
echo "Attempt $attempt got HTTP 429, retrying..." >&2
sleep $(( attempt * 10 ))
done
}

@test "covers all exercise scripts" {
local missing
missing=0
Expand All @@ -21,64 +34,64 @@ teardown() {
}

@test "outputs the HTML with Star Wars products" {
run uv run -q --with=httpx python lego.py
run_retry uv run -q --with=httpx python lego.py

[[ "$output" == *"Millennium Falcon"* ]]
}

@test "counts the number of F1 Academy teams" {
run uv run -q --with=httpx --with=beautifulsoup4 python f1academy_teams.py
run_retry uv run -q --with=httpx --with=beautifulsoup4 python f1academy_teams.py

[[ "$output" == "6" ]]
}

@test "counts the number of F1 Academy drivers" {
run uv run -q --with=httpx --with=beautifulsoup4 python f1academy_drivers.py
run_retry uv run -q --with=httpx --with=beautifulsoup4 python f1academy_drivers.py

[[ "$output" == "18" ]]
}

@test "lists IMO countries" {
run uv run -q --with=httpx --with=beautifulsoup4 python imo_countries.py
run_retry uv run -q --with=httpx --with=beautifulsoup4 python imo_countries.py

[[ "$output" == *$'Albania\nLibya\n'* ]]
[[ "$output" == *$'\nZimbabwe\nFaroes\n'* ]]
[[ $(echo "$output" | wc -l) -gt 5 ]]
}

@test "lists IMO countries with a single selector" {
run uv run -q --with=httpx --with=beautifulsoup4 python imo_countries_single_selector.py
run_retry uv run -q --with=httpx --with=beautifulsoup4 python imo_countries_single_selector.py

[[ "$output" == *$'Albania\nLibya\n'* ]]
[[ "$output" == *$'\nZimbabwe\nFaroes\n'* ]]
[[ $(echo "$output" | wc -l) -gt 5 ]]
}

@test "lists Guardian F1 article titles" {
run uv run -q --with=httpx --with=beautifulsoup4 python guardian_f1_titles.py
run_retry uv run -q --with=httpx --with=beautifulsoup4 python guardian_f1_titles.py

[[ "$output" == *' F1 '* ]]
[[ $(echo "$output" | wc -l) -gt 5 ]]
}

@test "prints warehouse stock counts" {
run uv run -q --with=httpx --with=beautifulsoup4 python warehouse_units.py
run_retry uv run -q --with=httpx --with=beautifulsoup4 python warehouse_units.py

[[ "$output" == *$'JBL Flip 4 Waterproof Portable Bluetooth Speaker | 672\n'* ]]
[[ "$output" == *$'Sony XBR-950G BRAVIA 4K HDR Ultra HD TV | 76\n'* ]]
[[ $(echo "$output" | wc -l) -gt 5 ]]
}

@test "prints warehouse stock counts using regex" {
run uv run -q --with=httpx --with=beautifulsoup4 python warehouse_units_regex.py
run_retry uv run -q --with=httpx --with=beautifulsoup4 python warehouse_units_regex.py

[[ "$output" == *$'JBL Flip 4 Waterproof Portable Bluetooth Speaker | 672\n'* ]]
[[ "$output" == *$'Sony XBR-950G BRAVIA 4K HDR Ultra HD TV | 76\n'* ]]
[[ $(echo "$output" | wc -l) -gt 5 ]]
}

@test "prints Guardian F1 titles with publish dates" {
run uv run -q --with=httpx --with=beautifulsoup4 python guardian_publish_dates.py
run_retry uv run -q --with=httpx --with=beautifulsoup4 python guardian_publish_dates.py

[[ "$output" == *' F1 '* ]]
[[ "$output" == *' | Mon '* ]] # has info about date, Mondays are very likely
Expand All @@ -88,27 +101,27 @@ teardown() {
@test "filters products from JSON" {
echo '[{"title":"Premium Speakers","min_price":75000,"price":75000},{"title":"Budget Headphones","min_price":25000,"price":25000}]' > products.json

run uv run python process_products_json.py
run_retry uv run python process_products_json.py

[[ "$output" == "{'title': 'Premium Speakers', 'min_price': 75000, 'price': 75000}" ]]
}

@test "lists WTA player links" {
run uv run -q --with=httpx --with=beautifulsoup4 python wta_tennis_links.py
run_retry uv run -q --with=httpx --with=beautifulsoup4 python wta_tennis_links.py

[[ "$output" == *'https://www.wtatennis.com/players/'* ]]
[[ $(echo "$output" | wc -l) -gt 10 ]]
}

@test "lists Guardian F1 article links" {
run uv run -q --with=httpx --with=beautifulsoup4 python guardian_f1_links.py
run_retry uv run -q --with=httpx --with=beautifulsoup4 python guardian_f1_links.py

[[ "$output" == *'https://www.theguardian.com/sport/'* ]]
[[ $(echo "$output" | wc -l) -gt 5 ]]
}

@test "lists WTA player birthplaces" {
run uv run -q --with=httpx --with=beautifulsoup4 python wta_tennis_players.py
run_retry uv run -q --with=httpx --with=beautifulsoup4 python wta_tennis_players.py

[[ "$output" == *'https://www.wtatennis.com/players/'* ]]
[[ "$output" == *' | '* ]]
Expand All @@ -117,7 +130,7 @@ teardown() {
}

@test "lists Guardian F1 authors" {
run uv run -q --with=httpx --with=beautifulsoup4 python guardian_f1_authors.py
run_retry uv run -q --with=httpx --with=beautifulsoup4 python guardian_f1_authors.py

[[ $(echo "$output" | wc -l) -gt 5 ]]
[[ "$output" == *' F1 '* ]]
Expand All @@ -132,7 +145,7 @@ teardown() {
}

@test "lists Python database jobs" {
run uv run -q --with=httpx --with=beautifulsoup4 python python_jobs_database.py
run_retry uv run -q --with=httpx --with=beautifulsoup4 python python_jobs_database.py

[[ "$output" == *"'title': '"* ]]
[[ "$output" == *"'company': '"* ]]
Expand All @@ -141,13 +154,13 @@ teardown() {
}

@test "counts total eurozone population" {
run uv run -q --with=httpx --with=beautifulsoup4 python eurozone_population.py
run_retry uv run -q --with=httpx --with=beautifulsoup4 python eurozone_population.py

[[ "$output" -gt 300000000 ]]
}

@test "scrapes F1 Academy driver details with Crawlee" {
run uv run -q --with=crawlee[beautifulsoup] python crawlee_f1_drivers.py
run_retry uv run -q --with=crawlee[beautifulsoup] python crawlee_f1_drivers.py

(( status == 0 ))
[[ -f dataset.json ]]
Expand All @@ -157,7 +170,7 @@ teardown() {
}

@test "scrapes Netflix user scores with Crawlee" {
run uv run -q --with=crawlee[beautifulsoup] python crawlee_netflix_ratings.py
run_retry uv run -q --with=crawlee[beautifulsoup] python crawlee_netflix_ratings.py

(( status == 0 ))
[[ -f dataset.json ]]
Expand Down
Loading