Gunnar

nordisk/monetize

crates/mock-upcloud/Cargo.toml

# **`mock-upcloud` — UpCloud API 1.3, faithfully wrong.**
#
# A real HTTP server that answers UpCloud's 1.3 surface from a state machine, so
# the whole money-to-disk path can be driven tens of thousands of times without a
# token, an account or a card. It is OPEN because the API it imitates is public
# and the defects it reproduces are the provider's, not ours; nothing in here
# knows a vetra price, a vetra plan or a vetra account.
#
# ## Why it lives in `monetize` and not in `gunnar` or `monetize-impl`
#
# `monetize-cloud` owns the Cloud seam. The provider this fakes is the
# one `monetize-cloud-impl` implements, and the sandbox cloud beside it already
# prices off a real UpCloud list — so the two ends of the same seam are already
# in this workspace's vocabulary. `gunnar/deploy/upcloud` holds a SCRIPTED
# transport (`fake.rs`): it proves a call ORDER inside one process and cannot be
# pointed at over a socket, has no state machine, and can never answer a poll
# differently the third time. A mock that a real binary reaches by URL has to be
# a server, and a server has to live where both consumers can dev-depend on it.
# The harness that points the PRIVATE plugin at this mock stays private
# (`monetize-impl/crates/monetize-cloud-impl/examples/upcloud_storm.rs`); the
# mock does not.
#
# ## Run
#
#     cargo run -p mock-upcloud -- --port 8099                    # faithful timings
#     cargo run -p mock-upcloud -- --port 8099 --speed 0          # virtual time, no waiting
#     cargo run -p mock-upcloud -- --port 8099 --seed 41          # seeded fault weather
#     UPCLOUD_API_BASE=http://127.0.0.1:8099 UPCLOUD_TOKEN=ucat_mock …
[package]
name = "mock-upcloud"
description = "A faithful fake of the UpCloud API 1.3 — the lies included — backed by real KVM guests"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
repository = "https://git.gunnar.rs/nordisk/monetize"
license.workspace = true
authors.workspace = true
publish = true

[[bin]]
name = "mock-upcloud"
path = "src/bin/mock-upcloud.rs"

# NO FEATURES, AND NO `draupnir` DEPENDENCY HERE — on purpose, and the reason is
# a measurement rather than a preference. draupnir's live KVM backend is
# `backend-tunnr`, and its manifest spells the dependency
# `tunnr-vm = { path = "../tunnr", version = "0.1" }`. tunnr is NOT on crates.io.
# An optional dependency is still RESOLVED into the lockfile whether or not its
# feature is on, so naming draupnir here — even behind an off-by-default feature
# — would make `cargo metadata` on the whole `monetize` workspace require a
# `tunnr` checkout in a sibling directory. That is exactly the defect this
# workspace already paid for once and wrote down: Skidbladnir was a path dep and
# a fresh clone of monetize did not resolve, with an error naming a directory
# that has nothing to do with this repository.
#
# So the KVM half is a SEAM here (`kvm::GuestEngine`) and a DETACHED crate in
# `kvm-guest/`, which has its own `[workspace]` and path-deps draupnir and tunnr
# on a box that has them. The open workspace keeps a lock that resolves from
# crates.io alone; the real guest still really boots.

[features]
# **Test-only.** `Mock::inject`: answer the next N matching requests with a named
# error. An INJECTION, not a behaviour — it claims nothing about UpCloud. Off by
# default and never on in the binary: a mock built without it cannot invent an
# answer. A client's tests turn it on in their dev-dependency.
test-inject = []

[dependencies]
tokio = { workspace = true, features = ["net", "io-util", "signal", "rt-multi-thread"] }
serde.workspace = true
serde_json.workspace = true
# The trait its in-process face answers (`face::FakeUpCloud`) — edda's ONE UpCloud
# surface, with the wire OFF: this crate answers the API, it never calls one,
# so no HTTP client comes with it into the open workspace.
upcloud-api = { version = "0.1.3", default-features = false }
# The utility L2 (`fabric`): several listeners on one multicast port need
# SO_REUSEADDR/SO_REUSEPORT, which std's UdpSocket cannot set. Already in the lock.
socket2 = { version = "0.6", features = ["all"] }

[dev-dependencies]
# The mock is asserted against with a blocking client, the same stack the real
# plugin reaches it with.
reqwest = { version = "0.13", default-features = false, features = ["rustls-no-provider", "json", "blocking"] }
# reqwest 0.13 has no bundled roots and its `rustls` feature means aws-lc-rs (C) + the
# OS trust store; ring + the Mozilla bundle keep 0.12 `rustls-tls` behaviour (see the
# `tls_roots` helpers). 2026-10-02.
rustls = { version = "0.23", default-features = false, features = ["ring", "std"] }
webpki-root-certs = { version = "1" }