diff --git a/.github/workflows/fastly-edge-assignments.yml b/.github/workflows/fastly-edge-assignments.yml new file mode 100644 index 0000000..26a4826 --- /dev/null +++ b/.github/workflows/fastly-edge-assignments.yml @@ -0,0 +1,64 @@ +name: Fastly Edge Assignments + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + cargo_build_and_test: + runs-on: ubuntu-latest + defaults: + run: + working-directory: fastly-edge-assignments + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + # Cache Rust toolchain and dependencies + - uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + ~/.rustup/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + target: wasm32-wasi + + # Install tools only if not cached + - name: Install Tools + run: | + if ! command -v cargo-nextest &> /dev/null; then + cargo install cargo-nextest + fi + if ! command -v fastly &> /dev/null; then + wget https://github.com/fastly/cli/releases/download/v10.17.0/fastly_10.17.0_linux_amd64.deb + sudo apt install ./fastly_10.17.0_linux_amd64.deb + fi + if ! command -v viceroy &> /dev/null; then + cargo install viceroy + fi + + # Build WASM target + - run: make build + + # Run unit and integration tests + - run: make test diff --git a/fastly-edge-assignments/.cargo/config.toml b/fastly-edge-assignments/.cargo/config.toml index 04fad80..d7f64da 100644 --- a/fastly-edge-assignments/.cargo/config.toml +++ b/fastly-edge-assignments/.cargo/config.toml @@ -1,6 +1,9 @@ [build] target = "wasm32-wasi" +[target.wasm32-wasi] +runner = "viceroy run -C fastly.toml -- " + [patch.crates-io] # Local override for development. eppo_core = { path = '../eppo_core' } diff --git a/fastly-edge-assignments/Makefile b/fastly-edge-assignments/Makefile new file mode 100644 index 0000000..fba0b5e --- /dev/null +++ b/fastly-edge-assignments/Makefile @@ -0,0 +1,31 @@ +SHELL := bash +.ONESHELL: +.SHELLFLAGS := -eu -o pipefail -c +.DELETE_ON_ERROR: +MAKEFLAGS += --warn-undefined-variables +MAKEFLAGS += --no-builtin-rules + +WASM_TARGET=wasm32-wasi +BUILD_DIR=target/$(WASM_TARGET)/release +WASM_FILE=$(BUILD_DIR)/$(FASTLY_PACKAGE).wasm + +# Help target for easy documentation +.PHONY: help +help: + @echo "Available targets:" + @echo " build - Build the WASM target" + @echo " test - Run unit and integration tests" + @echo " clean - Clean all build artifacts" + +.PHONY: clean +clean: + rm -rf bin pkg + +.PHONY: build +build: + rustup target add $(WASM_TARGET) + fastly compute build + +.PHONY: test +test: + cargo nextest run diff --git a/fastly-edge-assignments/README.md b/fastly-edge-assignments/README.md index 6c6800f..1846cef 100644 --- a/fastly-edge-assignments/README.md +++ b/fastly-edge-assignments/README.md @@ -1,3 +1,33 @@ # Eppo Assignments on Fastly Compute@Edge TODO: Add a description + +## Development + +Install Rust toolchain: + +`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh` + +Install Fastly CLI: + +`brew install fastly/tap/fastly` + +https://www.fastly.com/documentation/reference/tools/cli/ + +Install Viceroy: + +`cargo install viceroy` + +Build with Fastly: + +`make build` + +## Testing + +Install nextest: + +`cargo binstall cargo-nextest --secure` + +Run tests: + +`make test` diff --git a/fastly-edge-assignments/src/main.rs b/fastly-edge-assignments/src/main.rs index 665a755..9381a17 100644 --- a/fastly-edge-assignments/src/main.rs +++ b/fastly-edge-assignments/src/main.rs @@ -3,8 +3,17 @@ mod handlers; use fastly::http::{Method, StatusCode}; use fastly::{Error, Request, Response}; -#[fastly::main] -fn main(req: Request) -> Result { +#[cfg(test)] +const TEST_HOST: &str = "test-host"; + +fn main() -> Result<(), Error> { + let ds_req = Request::from_client(); + let us_resp = handler(ds_req)?; + us_resp.send_to_client(); + Ok(()) +} + +fn handler(req: Request) -> Result { // Handle CORS preflight requests if req.get_method() == Method::OPTIONS { return Ok(Response::from_status(StatusCode::NO_CONTENT) @@ -26,3 +35,44 @@ fn main(req: Request) -> Result { .with_header("Access-Control-Allow-Origin", "*") .with_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")) } + +#[test] +fn test_health() { + let req = fastly::Request::get(&format!("https://{}/health", TEST_HOST)); + let resp = handler(req).expect("request succeeds"); + assert_eq!(resp.get_status(), StatusCode::OK); + assert_eq!(resp.into_body_str(), "OK"); +} + +#[test] +fn test_cors_headers() { + let req = Request::get(&format!("https://{}/health", TEST_HOST)); + let resp = handler(req).expect("request succeeds"); + + assert_eq!(resp.get_header("Access-Control-Allow-Origin").unwrap(), "*"); + assert_eq!( + resp.get_header("Access-Control-Allow-Methods").unwrap(), + "GET, POST, OPTIONS" + ); +} + +#[test] +fn test_options_request() { + let req = Request::new( + Method::OPTIONS, + &format!("https://{}/assignments", TEST_HOST), + ); + let resp = handler(req).expect("request succeeds"); + + assert_eq!(resp.get_status(), StatusCode::NO_CONTENT); + assert_eq!(resp.get_header("Access-Control-Allow-Origin").unwrap(), "*"); + assert_eq!( + resp.get_header("Access-Control-Allow-Methods").unwrap(), + "GET, POST, OPTIONS" + ); + assert_eq!( + resp.get_header("Access-Control-Allow-Headers").unwrap(), + "Content-Type" + ); + assert_eq!(resp.get_header("Access-Control-Max-Age").unwrap(), "86400"); +}