Files
nolimit-discounts/docs/TESTING-AND-RELEASES.md
T

159 lines
6.8 KiB
Markdown
Raw Normal View History

# Testing, releases and automatic updates
Three questions, answered by three things in this repository.
| Question | Answer |
| --- | --- |
| Does it still work on the newest WordPress and WooCommerce? | `ci/site-test.sh`, run weekly by `compat.yml` against `latest`, `nightly` and `beta` |
| Can updates be pushed out automatically? | Yes — `includes/class-updater.php` plus `release.yml` |
| What happens when *I* press update in wp-admin? | Nothing surprising, because the release was tested against those versions before it was published |
---
## Running the tests
```sh
ci/static.sh # syntax, hygiene, versions, headers — seconds
ci/site-test.sh # a real WordPress + WooCommerce — a few minutes
ci/build-zip.sh # what a shop actually installs
```
`ci/site-test.sh` takes three knobs:
```sh
WP_VERSION=latest # or nightly, or an exact version like 7.0
WC_VERSION=latest # or beta, or an exact version like 11.1.0
PHP_VERSION=8.3 # any tag of the wordpress:cli image
```
It builds a throwaway site in Docker containers, installs WordPress and
WooCommerce at those versions, copies in **exactly the files the release zip
will contain**, activates the plugin, and runs `tests/fixtures.php` and
`tests/smoke.php` inside it with `wp eval-file`.
The tests use a real cart, real coupons and real `WC_Shipping_Rate` objects.
That is the point. A mocked cart keeps passing after WooCommerce has changed
underneath it, which is the one failure this whole directory exists to catch.
Everything is torn down afterwards — containers, volumes and the network — so
running it twice leaves nothing behind.
### Deprecations count as failures
The site test switches on `WP_DEBUG` and fails the build on any notice logged
from this plugin's directory. That is how a WordPress or PHP upgrade actually
breaks a plugin: nothing fails, a notice appears, and two releases later it is
a fatal. Notices from WordPress core and WooCommerce are ignored — failing on
those would make the build red for something nobody here can fix, and a build
that is always red is a build that gets ignored.
---
## The weekly compatibility run
`compat.yml` runs the matrix every Monday at 06:00 and on demand:
| WordPress | WooCommerce | PHP | What it tells you |
| --- | --- | --- | --- |
| latest | latest | 8.3 | today's shop |
| **nightly** | latest | 8.3 | the next WordPress, weeks early |
| latest | **beta** | 8.3 | the next WooCommerce, weeks early |
| latest | latest | 7.4 | the floor the plugin header promises |
| latest | latest | 8.4 | where deprecations appear first |
The `beta` cell falls back to the stable release when there is no open beta, so
it is never silently skipped.
When the whole matrix passes, a second job compares `Tested up to:` and
`WC tested up to:` against the current releases and opens a pull request if
they are behind. It runs only after the matrix is green, because those headers
are a claim — WooCommerce shows `WC tested up to` to the shop owner as an
untested-plugin banner, and that banner is what they read before deciding
whether to press update.
The schedule fires on both forges; the matrix job skips the scheduled run on
Gitea so the single self-hosted runner is not asked to repeat GitHub's work.
`workflow_dispatch` still works there.
---
## Cutting a release
1. Bump `Version:` in the plugin header. `ci/static.sh` checks that
`WC_EDM_VERSION` and the readme's `Stable tag:` agree with it — WordPress
compares the update feed against the header and nothing else, so a
disagreement means the update is offered again immediately after it is
installed, forever.
2. Add a changelog section to `README.txt`.
3. Merge to `main`.
4. Tag it **annotated**, because the message is the release notes:
```sh
git tag -a v1.4.0 -m "..."
git push origin v1.4.0 # origin pushes to GitHub and Gitea both
```
`release.yml` then runs the static checks, installs the plugin into a real
WordPress + WooCommerce and runs the full test suite, builds the zip, publishes
the release, and finally **reads the feed back anonymously** to prove a shop
can actually download it.
Two things in there are guards against bugs that are invisible without them:
- **The tag message is re-fetched before it is read.** `actions/checkout`
fetches every tag correctly and then overwrites the triggering one with the
commit SHA, turning an annotated tag into a lightweight one. Reading it then
returns the *commit* message — on a merge commit, "Merge pull request #12
from …". Nothing fails; the release is just published with the wrong text.
- **The anonymous read-back sends no credentials.** The two ways the feed
breaks — a private repository and a release with no zip attached — both
leave a release page that looks perfectly fine to anyone logged in.
---
## How the automatic updates work
The plugin header carries:
```
Update URI: https://gitea.nolimit.dk/kw/woocommerce-email-discount-manager
```
`includes/class-updater.php` hooks `update_plugins_gitea.nolimit.dk`, the
filter WordPress fires for plugins whose `Update URI` points at that host. It
reads `/api/v1/repos/kw/<repo>/releases/latest`, takes the tag as the version
and the attached `.zip` as the package, and hands both to WordPress. WordPress
does the version comparison, the download, the unpacking and the rollback.
That means all of this works with no extra code:
- the update notice on **Plugins** and **Dashboard → Updates**
- the **update now** link
- the per-plugin **Enable auto-updates** link — WordPress builds that from the
plugins it has an answer for, which is why the updater answers even when the
installed version is already current
- `wp plugin update woocommerce-email-discount-manager` over WP-CLI
`Update URI` earns its place a second way: since WordPress 5.8, a plugin that
declares one is **never asked about on wordpress.org**. Without it, a .org
plugin that happened to share this slug could be pushed onto a shop running
this one.
### One-time setup
The repository on Gitea must be **public**, because every shop reads that feed
without credentials. Settings → make it public. Nothing in a GPL plugin zip is
secret, and the release workflow fails loudly with this exact instruction if it
is not done.
If you would rather keep the source private, create a separate public repo that
holds only releases and point `Update URI` at that instead — the updater
derives everything else from the header, so that is a one-line change.
### It reports nothing
The update check sends a plain unauthenticated GET for a file every visitor to
that repository can read. The user agent is overridden on purpose: WordPress's
default one is `WordPress/7.1; https://the-shop.example`, which would tell the
update host which shops are running this plugin. Nothing else about the site
leaves it, ever.