plugintest

package
v0.51.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 4, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package plugintest is the test kit that ships with the filex plugin SDK.

A plugin is a wasm module filex loads, so the obvious way to test one is to build it and install it — which is slow, needs a host, and happens after the mistake is already in the module. plugintest runs the plugin's own Go functions in-process, behind a fake filex, so `go test` answers the questions that matter before `plugin.wasm` exists:

  • does the action do the right thing, and does it degrade when a permission is missing or an engine is absent?
  • is every screen drawable by filex's renderer — known node types, a readable choice instead of a dropdown, one primary button per step, conditions that point at fields that exist?
  • does every string carry every language the manifest promises?
  • did a screen change shape without anybody noticing (golden screens)?

The shape of a test

func harness(t *testing.T) *plugintest.Harness {
	h := plugintest.New(myapp.Plugin())      // manifest + actions + views
	h.Host.InstallEngine("ffmpeg")           // what this fake server has
	return h
}

func TestOptionsScreen(t *testing.T) {
	h := harness(t)
	s, err := h.Open("options", plugintest.File{Name: "clip.mov"})
	if err != nil {
		t.Fatal(err)
	}
	plugintest.CheckSurface(t, h.Manifest(), s)   // structure
	plugintest.CheckLanguages(t, h.Manifest(), s) // every declared language
	plugintest.Golden(t, "options", s)            // shape, vs testdata/golden
}

The fake host

Host is filex's host side in memory: files, settings, per-file state, locks, engines, signing, notifications, mail, HTTP and shares. It uses the manifest's permissions as the administrator's grants and answers a call it was not granted with the real code (`permission_denied`), and it refuses from a screen what only an action job may do. Anything else would make the kit lie: the plugin would pass its tests and fail on a real instance.

What it cannot do

The host functions in pluginkit only answer inside wasm; off-wasm they return ErrNotWasm. So a plugin reaches the fake host the way it reaches the real one in tests everywhere else — through a small interface it takes as a parameter (see the `Host` interface in filex-convert's internal/job). *Host satisfies such an interface method-for-method, with the same names and signatures as pluginkit's own functions.

Index

Constants

This section is empty.

Variables

View Source
var FieldTypes = map[string]bool{
	"string": true, "password": true, "int": true, "bool": true,
	"select": true, "text": true, "date": true,
}

FieldTypes are the input types a form field may declare.

View Source
var GoldenDir = filepath.Join("testdata", "golden")

GoldenDir is where the stored screens live, relative to the test's package directory.

View Source
var HumanKeys = map[string]bool{
	"label": true, "help": true, "placeholder": true, "text": true,
	"title": true, "toast": true, "message": true, "empty": true,
	"hint": true, "description": true, "summary": true, "note": true,
	"caption": true, "subject": true, "heading": true, "reason": true,
}

HumanKeys are the property names that hold a string a person reads. The locale comparison only looks at these (and at every Text): an id, a key or an option's value is the same in every language by design, and warning about those would bury the one line that matters.

View Source
var KnownEngines = map[string]bool{
	"ffmpeg": true, "imagemagick": true, "office": true, "libreoffice": true,
	"ghostscript": true, "poppler": true, "rsvg": true,
}

KnownEngines are the engines a plugin may ask for. `office` is the office engine (the connected ONLYOFFICE Document Server, since filex 0.50); `libreoffice` is its earlier name, still accepted for the same engine.

View Source
var LayoutNodes = map[string]bool{"row": true}

LayoutNodes are the node types that may carry children.

View Source
var NodeTypes = map[string]bool{
	"text": true, "divider": true, "row": true, "form": true, "steps": true,
	"list": true, "progress": true, "people-picker": true, "pin-input": true,
	"file-chooser": true, "preview": true, "pdf-fields": true, "signature-pad": true,
}

NodeTypes is filex's node catalogue — the components a surface may ask for. A type that is not here is drawn by nothing: the screen comes up with a hole in it. Kept in step with the table in docs/PLUGIN-KIT.md.

View Source
var Permissions = map[string]bool{
	"files:read": true, "files:write": true, "files:lock": true, "sign": true,
	"mail:send": true, "notify:send": true, "users:lookup": true,
	"settings": true, "state": true, "public_pages": true, "schedule": true,
}

Permissions is the closed set a manifest may ask for; the parameterised ones (`http:<host>`, `engines:<name>`) are checked by prefix. `events:<name>` is refused — see manifest.go. ⚠ It must track wasmplugin's own set (internal/wasmplugin/permissions.go). `schedule` was missing here for a release after the host accepted it, so a plugin asking for the hourly wake-up had its manifest REFUSED by its own tests while the server installed it happily — the kit, not the app, was wrong. Add the name here in the same change that adds it there.

Functions

func AsText

func AsText(v any, langs []string) (wire.Text, bool)

AsText recognises a Text: every key is a language tag, every value is a string, and it either carries `en` (required everywhere) or speaks only languages the plugin declared. The second condition is what keeps a row of cells like {"id": "…"} from being read as Indonesian.

func CheckJobResult added in v0.50.0

func CheckJobResult(t TB, m wire.Manifest, out *wire.ActionRunOutput)

CheckJobResult fails t when filex would not do what a job's answer asks (InspectJobResult). Warnings are logged.

func CheckLanguages

func CheckLanguages(t TB, m wire.Manifest, s *wire.Surface)

CheckLanguages fails t when a Text on the screen is missing a language the manifest promised.

func CheckLocaleParity

func CheckLocaleParity(t TB, byLocale map[string]*wire.Surface)

CheckLocaleParity fails t when the same screen, drawn once per declared language, is not the same screen: a section only one language has, or a string that is empty in one. Strings that came out identical in two languages are reported as warnings — that is what a missing translation looks like from the outside.

func CheckLocaleParityOpts

func CheckLocaleParityOpts(t TB, byLocale map[string]*wire.Surface, opts LangOpts)

CheckLocaleParityOpts is CheckLocaleParity with an allowlist for strings that are the same in every language on purpose (format names, brands).

func CheckManifest

func CheckManifest(t TB, m wire.Manifest)

CheckManifest measures the manifest against what filex refuses at install, and fails t on a breach.

func CheckManifestLanguages

func CheckManifestLanguages(t TB, m wire.Manifest)

CheckManifestLanguages fails t when a Text in the manifest itself — the plugin's label, an action's label, a public page's title — is missing a declared language. This is the one the host also checks at install; a test says so before the install does.

func CheckRegistered

func CheckRegistered(t TB, p *pluginkit.Plugin)

CheckRegistered measures the plugin against its own manifest: every action, view and public page the manifest advertises has a function behind it, and nothing is registered that the manifest never declared. A menu row that opens a screen nobody wrote is the failure this catches.

func CheckSchedule

func CheckSchedule(m wire.Manifest, in *wire.TickInput, out *wire.TickOutput) error

CheckSchedule applies the bounds filex publishes to an answer, and returns every complaint at once rather than the first.

It is a warning, not the enforcement — the host is what refuses — but it is the same set of numbers (wire.Schedule*) and the same shapes, checked where a failing `go test` costs seconds instead of an hour of wall clock.

func CheckSurface

func CheckSurface(t TB, m wire.Manifest, s *wire.Surface)

CheckSurface runs every structural rule against a screen and fails t on a breach. Warnings are logged.

func ChoiceValues

func ChoiceValues(s *wire.Surface, key string) []string

ChoiceValues is Choices reduced to the option values.

func Choices

func Choices(s *wire.Surface, key string) ([]wire.FieldOption, bool)

Choices answers the options a select renders as buttons. It is how a test reads a picker without knowing how it is drawn: `len(Choices(s, "target"))` is the number of buttons the person sees, and their labels are what the buttons say. The second result is false when there is no such field or it is not a select.

func Code

func Code(err error) string

Code answers the wire.Err* code of a host failure, or "".

func Field

func Field(s *wire.Surface, key string) (wire.Field, bool)

Field finds a field by key anywhere on the screen.

func FieldsOf

func FieldsOf(n wire.Node) []wire.Field

FieldsOf reads a form node's fields, whether the plugin built them as wire.Field values or as plain maps.

func Forms

func Forms(s *wire.Surface) []wire.Node

Forms lists the form nodes of a surface.

func Golden

func Golden(t TB, name string, v any)

Golden compares v (a surface, a manifest, an action's output — anything that marshals) against testdata/golden/<name>.json. With -update it writes the file instead.

func GoldenWith

func GoldenWith(t TB, name string, v any, scrub func([]byte) []byte)

GoldenWith is Golden with a chance to scrub the volatile parts — a token, a timestamp, a temporary path — out of the JSON before it is compared, so the stored screen stays stable.

func HiddenKeys

func HiddenKeys(fields []wire.Field, values map[string]any) []string

HiddenKeys is the other half of VisibleKeys: the fields `show_when` is keeping off the screen. The host drops their values before a job runs.

func IsCode

func IsCode(err error, code string) bool

IsCode reports whether err is a host failure with that wire.Err* code.

func Languages

func Languages(m wire.Manifest) []string

Languages answers a manifest's declared languages, English by default.

func Nodes

func Nodes(s *wire.Surface, typ string) []wire.Node

Nodes lists every node of a type, in order.

func PrimaryAction

func PrimaryAction(s *wire.Surface) (wire.SurfaceAction, bool)

PrimaryAction answers the step's one primary button.

func RequiredKeys

func RequiredKeys(fields []wire.Field, values map[string]any) []string

RequiredKeys is the set of fields that must be filled for those values (`required`, plus `required_when` where the rule holds).

func Submitted

func Submitted(fields []wire.Field, values map[string]any) map[string]any

Submitted is what the host would hand the job: the values of the fields that are visible, with everything else dropped — the same rule the host re-applies at submit, so a test can prove a hidden box cannot smuggle a value through.

func Updating

func Updating() bool

Updating reports whether this run rewrites the golden screens (-update, or PLUGINTEST_UPDATE=1).

func ValuesOf

func ValuesOf(n wire.Node) map[string]any

ValuesOf reads a form node's current values.

func VisibleKeys

func VisibleKeys(fields []wire.Field, values map[string]any) []string

VisibleKeys is the set of field keys a person can see for those values, after `show_when` has had its say.

func WalkNodes

func WalkNodes(nodes []wire.Node, fn func(where string, n wire.Node))

WalkNodes visits every node of a surface, depth first, with a readable path for messages.

Types

type EngineCall

type EngineCall struct {
	Request pluginkit.EngineRequest
	Result  *pluginkit.EngineResult
	Err     error
}

EngineCall is one engine_run call, recorded in order.

type File

type File struct {
	Name string
	Data []byte
	Mime string
	// Path is the adapter-qualified spelling (`docs://a/b.pdf`); the
	// harness invents one when it is empty.
	Path string
	// Rel is the storage-relative path; defaults to Name.
	Rel string
	// ReadOnly puts the file on a storage that takes no writes
	// (wire.FileRef.ReadOnly).
	ReadOnly bool
}

File is one file handed to a call.

type Finding

type Finding struct {
	Severity Severity
	Where    string
	Message  string
}

Finding is one thing the kit noticed, with where it is.

func (Finding) String

func (f Finding) String() string

type Harness

type Harness struct {
	// Plugin is what the module registers with pluginkit.Run.
	Plugin *pluginkit.Plugin
	// Host is the fake instance. Wire it before a call: settings, engines,
	// directory entries, scripted engine answers.
	Host *Host
	// Locale is the language a call carries when the event does not name
	// one (default "en").
	Locale string
	// Actor is the person the call runs as. By default an administrator, and
	// so holding every one of the manifest's `user_permissions`
	// (Actor.Permissions), as filex decides it; clear Permissions (or set
	// the ids a test needs) to draw a screen for somebody who holds fewer.
	Actor wire.Actor
	// contains filtered or unexported fields
}

Harness is a plugin under test with a fake filex behind it.

func New

func New(p *pluginkit.Plugin) *Harness

New builds a harness around a registered plugin. For the manifest-only checks a bare `&pluginkit.Plugin{Manifest: m}` is enough.

func NewFor

func NewFor(m wire.Manifest, build func(*Host) *pluginkit.Plugin) *Harness

NewFor is New for a plugin that takes its host as a parameter — the shape a plugin needs anyway, because pluginkit's host functions only answer inside wasm. The fake host is built from the manifest first and handed to the plugin, so the grants are the manifest's own:

h := plugintest.NewFor(myapp.Manifest(), func(host *plugintest.Host) *pluginkit.Plugin {
	return myapp.Plugin(host)
})

func (*Harness) Act

func (h *Harness) Act(viewID, actionID string, state, values map[string]any) (*wire.Surface, error)

Act is View with an `action` event: a footer button that is not the primary one, or a row action (pass `row_id` in data).

func (*Harness) Change

func (h *Harness) Change(viewID string, state, values map[string]any) (*wire.Surface, error)

Change is View with a `change` event carrying the form's values.

func (*Harness) Do

func (h *Harness) Do(actionID string, params map[string]any, files ...File) (*wire.ActionRunOutput, error)

Do is Run with the parameters a screen would have collected.

func (*Harness) Event

func (h *Harness) Event(event string, files ...File) *wire.ViewEventInput

Event builds a view event on the current selection.

func (*Harness) InLocales

func (h *Harness) InLocales(viewID string, ev *wire.ViewEventInput) (map[string]*wire.Surface, error)

InLocales draws the same screen once per declared language. What comes back is what CheckLocaleParity reads: a string that is empty in one language, or a section that only one language has, is a screen half in the wrong language — the complaint this kit exists for.

func (*Harness) Input

func (h *Harness) Input(actionID string, files ...File) *wire.ActionRunInput

Input builds an ActionRunInput with the manifest's own defaults filled in: the action's output mode, the current selection, the actor, the locale, the non-secret settings and the engine map.

func (*Harness) Languages

func (h *Harness) Languages() []string

Languages is what the manifest promises to speak (["en"] when it says nothing).

func (*Harness) Manifest

func (h *Harness) Manifest() wire.Manifest

Manifest is what describe would answer: the registered manifest with manifest_version filled in.

func (*Harness) Open

func (h *Harness) Open(viewID string, files ...File) (*wire.Surface, error)

Open is View with an `open` event.

func (*Harness) OpenInLocales

func (h *Harness) OpenInLocales(viewID string, files ...File) (map[string]*wire.Surface, error)

OpenInLocales is InLocales with an `open` event.

func (*Harness) Page

func (h *Harness) Page(pageID, token string, ev *wire.ViewEventInput) (*wire.Surface, error)

Page answers a public-page event on a link the plugin opened. The fake host is in share mode: not writable, but `state_set` and `share_state("")` answer for that link, as they do on a real page event.

func (*Harness) Press added in v0.50.0

func (h *Harness) Press(viewID string, s *wire.Surface, button string, values map[string]any) (*wire.Surface, error)

Press presses one of the screen's footer buttons THE WAY FILEX DOES, and answers the next screen. Prefer it to Submit and Act for anything a person clicks: those two let the test choose the event, and a test that chooses is a test that can choose wrong.

⚠⚠ filex posts the PRIMARY button as `submit` and every other one as `action`, with the button id in `action_id` - the full page, the dialog and an embedded explorer's popup alike (packages/core usePluginSurface `press`). filex-sign 0.1.0 shipped primary buttons ("Convert to PDF", "Open its Signatures panel", "Close the expired request") whose handlers listened for `action` only: a click redrew the same screen. Its tests pressed them with Act, so they were green the whole time. Press reads the button's weight off the screen the plugin drew, so the test sends what the person's click sends.

It also refuses the presses filex never sends, with an error rather than a quiet second screen:

  • a button the screen does not draw (a test pressing a button that was renamed or dropped is testing a screen nobody sees);
  • a disabled button;
  • the primary button while a visible required field is empty (the browser stops it there and points at the field; the host would answer 422 required).

The event carries the screen's own `state` back, and the values a person would be sending: what the screen's forms already hold (their `values`, else each field's `default`), with `values` laid over them and the values of fields `show_when` hides dropped.

func (*Harness) Queue

func (h *Harness) Queue(s *wire.Surface, files ...File) (*wire.ActionRunOutput, error)

Queue runs the job a surface asked for (`{job: …}`), with that job's params and output override — the second half of a wizard, without the host in between.

func (*Harness) Run

func (h *Harness) Run(actionID string, in *wire.ActionRunInput) (*wire.ActionRunOutput, error)

Run calls the action as filex would: the fake host is in job mode, so outputs, state, locks, engines and shares are allowed.

func (*Harness) Select

func (h *Harness) Select(files ...File) []wire.FileRef

Select registers the files a call is made on and answers their refs. Call it once per flow: Open, Submit and Input reuse the selection, so a wizard's three screens do not register the same file three times.

func (*Harness) Selection

func (h *Harness) Selection() []wire.FileRef

Selection is what Select registered.

func (*Harness) Submit

func (h *Harness) Submit(viewID string, state, values map[string]any) (*wire.Surface, error)

Submit is View with a `submit` event carrying the form's values.

func (*Harness) Tick

func (h *Harness) Tick(in *wire.TickInput) (*wire.TickOutput, error)

Tick runs the wake-up with an input you built yourself — a shorter window, a different clock, settings the administrator has not entered yet.

func (*Harness) TickInput

func (h *Harness) TickInput(now time.Time) *wire.TickInput

TickInput is the wake-up filex would hand the plugin at `now`.

func (*Harness) UICall added in v0.48.0

func (h *Harness) UICall(viewID, method string, params any, files ...File) (*wire.UICallOutput, error)

UICall is the app's own interface calling its module (`fx.call(method, params)`): the handler registered in Plugin.UI, in screen mode, with the current selection as the interface's files. The answer is what the interface would receive — the result, or the error it rejects with.

func (*Harness) View

func (h *Harness) View(viewID string, ev *wire.ViewEventInput) (*wire.Surface, error)

View answers a view event. The fake host is in screen mode: the plugin may read its inputs, but a screen that tries to write a file, take a lock or run an engine is refused exactly as filex refuses it.

func (*Harness) Wake

func (h *Harness) Wake(now time.Time) (*wire.TickOutput, error)

Wake runs the hourly wake-up as filex would at `now`: the window runs from `now` to the next hourly boundary, the settings and engines are this harness's, and the call is READ-ONLY — a `StateSet` from a tick is refused here exactly as the real host refuses it.

out, err := h.Wake(time.Date(2026, 9, 20, 3, 0, 0, 0, time.UTC))
require.NoError(t, err)
require.NoError(t, plugintest.CheckSchedule(h.Manifest(), h.TickInput(now), out))

type Host

type Host struct {

	// Recorded traffic, for assertions.
	ProgressLog []ProgressLine
	Notices     []pluginkit.Notice
	Mails       []Mail
	Engines     []EngineCall
	Requests    []pluginkit.HTTPRequest
	Logs        []string
	SettingsGot []string

	// EngineFn answers engine_run. The default produces one artefact per
	// name in req.Outputs (`engine:<name>` as bytes), or a single
	// `out.bin`. Set it to script failures, timeouts and real payloads.
	// It answers the office engine too when set; OfficeFn is the narrower
	// way to script that one.
	EngineFn func(n int, req pluginkit.EngineRequest) (*pluginkit.EngineResult, error)
	// OfficeFn is the fake document server behind the office engine
	// (`engines:office`, alias `engines:libreoffice`): one call per file of a
	// `--convert-to` command line the host has already read and accepted
	// (officecmd). Return the converted bytes, or an *OfficeRefusal for the
	// document server's error code (-7: it does not make that format from
	// that one). The default answers `office:<from>-><to>` and the input.
	OfficeFn func(c OfficeConversion) ([]byte, error)
	// HTTPFn answers http_request for a granted host. The default is a
	// 200 with an empty body.
	HTTPFn func(req pluginkit.HTTPRequest) (*pluginkit.HTTPResponse, error)

	// MailPerHour is the quota mail_send enforces (the real host: 60).
	MailPerHour int

	// SignUnavailable makes host_sign_info answer "not available" without
	// failing — an instance with no signing key.
	SignUnavailable string
	// SignBudget, when > 0, is how many cert_issue/host_sign calls succeed
	// before the host answers `busy` (the real host rate-limits signing).
	SignBudget int

	// MailConfigured false makes mail_send answer `unavailable`.
	MailConfigured bool
	// NotifyConfigured false makes notify_send answer `unavailable`.
	NotifyConfigured bool
	// MaxInputBytes / MaxOutputBytes are the per-file ceilings (0 = none).
	MaxInputBytes  int64
	MaxOutputBytes int64
	// BaseURL is the instance a share link points at.
	BaseURL string

	// Network is what asset_fetch can download: URL → bytes. A URL that is
	// not here answers `unavailable` (the server said 404).
	Network map[string][]byte
	// Offline makes every asset_fetch that needs the network fail with
	// `unavailable` — an installation with no internet. The cache still
	// answers.
	Offline bool
	// Downloads lists every URL asset_fetch actually downloaded, in order;
	// a call served from the cache adds nothing.
	Downloads []string
	// contains filtered or unexported fields
}

Host is the in-memory filex. Build one with NewHost, or take the one a Harness made (h.Host).

func NewHost

func NewHost(m wire.Manifest) *Host

NewHost builds the fake host for a manifest. The manifest's permissions are the grants: a call the manifest never asked for is refused, so a plugin cannot pass its tests by using a capability it forgot to declare.

func (*Host) AddInput

func (h *Host) AddInput(f File) wire.FileRef

AddInput registers a file the call may read and answers its ref.

func (*Host) AddUser

func (h *Host) AddUser(u pluginkit.User)

AddUser puts someone in the directory users_lookup searches.

func (*Host) Artefact

func (h *Host) Artefact(name string, data []byte) wire.OutputRef

Artefact registers a file "an engine produced" and answers its ref — what an EngineFn hands back in EngineResult.Outputs.

func (*Host) AssetFetch

func (h *Host) AssetFetch(url, sum string, maxBytes int64) (*pluginkit.Asset, error)

AssetFetch is asset_fetch: download url once into the app's cache, check it against the pinned sha256, hand back a ref to read it with. It enforces what the real host does — https, an `http:<host>` grant, no private address, max_bytes within 32 MiB, the hash — and serves a repeat from the cache without touching Network (Downloads does not grow).

func (*Host) Bytes

func (h *Host) Bytes(ref string) ([]byte, bool)

Bytes answers what a ref holds — an input, an output the plugin wrote or an engine artefact. It is how a test reads the result of a job.

func (*Host) CallSettings

func (h *Host) CallSettings() map[string]string

CallSettings is the settings map a call carries: everything the administrator entered EXCEPT the secret fields, which a plugin may only read through `settings_get`.

func (*Host) CertIssue

func (h *Host) CertIssue(commonName, email string, days int) (*pluginkit.IssuedCert, error)

CertIssue mints a real P-256 certificate from the fake tenant CA (permission `sign`, jobs only), so a signature a test produces actually verifies against the chain.

func (*Host) EngineAvailable

func (h *Host) EngineAvailable(name string) bool

EngineAvailable reports whether the engine is installed AND granted.

func (*Host) EngineRun

func (h *Host) EngineRun(req pluginkit.EngineRequest) (*pluginkit.EngineResult, error)

EngineRun runs one of the host's engines (permission `engines:<name>`, jobs only). Arguments are judged before anything else — including whether the engine exists — so a refused argument is a bug the author sees on every host, not only on the one that happens to have ffmpeg.

func (*Host) EnterJob

func (h *Host) EnterJob()

EnterJob makes the following calls behave like an action job (writable). The Harness does this around Run; call it directly only when driving the plugin's functions yourself.

func (*Host) EnterScreen

func (h *Host) EnterScreen()

EnterScreen makes the following calls behave like a view event: readable, but no outputs, state, locks, engines or shares.

func (*Host) EnterShare

func (h *Host) EnterShare(token string)

EnterShare makes the following calls behave like a page event on that link: not writable, but state_set and share_state("") answer for it.

func (*Host) FileLock

func (h *Host) FileLock(ref string, ttlDays int, reason string) (time.Time, error)

FileLock freezes a ref read-only (permission `files:lock`, jobs only).

func (*Host) FileLockMessage

func (h *Host) FileLockMessage(ref string, ttlDays int, key string, args map[string]string) (time.Time, error)

FileLockMessage is FileLock with a reason named by one of the manifest's `messages` (pluginkit.FileLockMessage). A key the manifest does not declare is refused, as the host refuses it. LockReason reads it back.

func (*Host) FileUnlock

func (h *Host) FileUnlock(ref string) error

FileUnlock lifts this plugin's lock (no-op when unlocked).

func (*Host) HTTPDo

HTTPDo performs an outbound request (permission `http:<host>`).

func (*Host) HostSign

func (h *Host) HostSign(keyRef string, digest []byte) ([]byte, error)

HostSign signs a sha256 digest with a host-held key. The answer is a DER-encoded ECDSA signature — a real one, verifiable with the leaf.

func (*Host) HostSignInfo

func (h *Host) HostSignInfo() (*pluginkit.SignInfo, error)

HostSignInfo answers whether this instance can sign (permission `sign`).

func (*Host) InputSize

func (h *Host) InputSize(ref string) int64

InputSize answers the size the host reported for a ref (-1 unknown).

func (*Host) InstallEngine

func (h *Host) InstallEngine(names ...string)

InstallEngine marks an engine as present on this server. Availability and the grant are separate: an engine the manifest never asked for stays refused even when installed. The office engine is "installed" when a document server is connected: InstallEngine("office") (or "libreoffice", the same engine).

func (*Host) InstalledEngines

func (h *Host) InstalledEngines() map[string]bool

InstalledEngines is the engine map a call carries (ActionRunInput.Engines / CallContext.Engines): installed AND granted, as the host reports it - the office engine under both of its names, as the real host does.

func (*Host) Key

func (h *Host) Key(ref string) (*IssuedKey, bool)

Key answers a certificate the fake CA issued.

func (*Host) KeyDestroy

func (h *Host) KeyDestroy(keyRef string) error

KeyDestroy discards the private key; the certificate stays.

func (*Host) LockReason

func (h *Host) LockReason(ref string) wire.Text

LockReason is the reason a lock on ref was taken with, in every language (FileLockMessage), or nil.

func (*Host) Locked

func (h *Host) Locked(ref string) (time.Time, bool)

Locked reports whether the plugin holds a lock on a ref, and until when.

func (*Host) Log

func (h *Host) Log(level, msg string)

Log records a plugin log line.

func (*Host) MailSend

func (h *Host) MailSend(to, subject, body string) error

MailSend sends a plain-text mail (permission `mail:send`, quota per hour).

func (*Host) MailSendIn

func (h *Host) MailSendIn(lang, to, subject, body string) error

MailSendIn is MailSend for a mail written in lang (pluginkit.MailSendIn).

func (*Host) NewSigner

func (h *Host) NewSigner(issued *pluginkit.IssuedCert) (*Signer, error)

NewSigner binds an issued certificate to this host.

func (*Host) NotifySend

func (h *Host) NotifySend(n pluginkit.Notice) (int64, error)

NotifySend raises a notification (permission `notify:send`).

func (*Host) Outputs

func (h *Host) Outputs() []wire.OutputRef

Outputs lists what the plugin wrote through file_create, in order.

func (*Host) PlatformSeal

func (h *Host) PlatformSeal() (*pluginkit.IssuedCert, error)

PlatformSeal is the installation's seal for this app: the same key on every call, which KeyDestroy refuses — as on the real host (seal.go).

func (*Host) Progress

func (h *Host) Progress(done, total int64, message string)

Progress records a job_progress call (no permission needed).

func (*Host) ReadInput

func (h *Host) ReadInput(ref string) ([]byte, error)

ReadInput reads a call-scoped ref (files:read).

func (*Host) SetSetting

func (h *Host) SetSetting(key, value string)

SetSetting writes one administrator setting (secret fields included).

func (*Host) Setting

func (h *Host) Setting(key string) (string, bool, error)

Setting reads one administrator setting (permission `settings`).

func (*Host) ShareByToken

func (h *Host) ShareByToken(token string) (*Share, bool)

ShareByToken answers a link the plugin opened.

func (*Host) ShareCreate

func (h *Host) ShareCreate(req pluginkit.PageCreate) (*pluginkit.PageCreated, error)

ShareCreate opens a public link for an outside participant (permission `public_pages`, jobs only).

func (*Host) ShareRevoke

func (h *Host) ShareRevoke(token string) error

ShareRevoke ends a link early.

func (*Host) ShareState

func (h *Host) ShareState(token string, out any) error

ShareState reads a link's durable record into out. Inside a page event token may be "" (the current link).

func (*Host) ShareStateSet

func (h *Host) ShareStateSet(token string, state any) error

ShareStateSet replaces a link's durable record (≤ 64 KiB).

func (*Host) Shares

func (h *Host) Shares() []*Share

Shares lists every link the plugin opened, in order.

func (*Host) State

func (h *Host) State(ref, key string) (string, bool)

State answers a per-file state key the plugin kept.

func (*Host) StateDelete

func (h *Host) StateDelete(ref, key string) error

StateDelete removes one key.

func (*Host) StateGet

func (h *Host) StateGet(ref, key string) (string, bool, error)

StateGet reads per-file state (permission `state`).

func (*Host) StateSet

func (h *Host) StateSet(ref, key, value string) error

StateSet writes per-file state (≤ 64 KiB; jobs and page events only).

func (*Host) UsersLookup

func (h *Host) UsersLookup(q string) ([]pluginkit.User, error)

UsersLookup searches the directory (permission `users:lookup`).

func (*Host) WriteOutput

func (h *Host) WriteOutput(name string, data []byte) (wire.OutputRef, error)

WriteOutput creates an output file with those bytes (files:write).

type IssuedKey

type IssuedKey struct {
	Ref        string
	CommonName string
	Email      string
	Cert       *x509.Certificate
	CertPEM    string
	Destroyed  bool
	// contains filtered or unexported fields
}

IssuedKey is a certificate the fake CA minted.

type LangOpts

type LangOpts struct {
	// Strict makes warnings fail the test too.
	Strict bool
	// SameAllowed are strings that are legitimately identical in every
	// language — format names ("PNG", "JPEG"), brands, units. Compare
	// exactly.
	SameAllowed []string
	// MinSameLen is the shortest string the "same in two languages" check
	// looks at (default 4): "OK", "PDF" and "%" are not translations that
	// went missing.
	MinSameLen int
}

LangOpts tunes the language checks.

type Mail

type Mail struct {
	To      string
	Subject string
	Body    string
	// Lang is what MailSendIn stated; "" for MailSend.
	Lang string
}

Mail is one mail_send call.

type OfficeConversion added in v0.50.0

type OfficeConversion struct {
	// Name is the input's name in the run directory ("in.docx"); From its
	// format, To the conversion API's output type ("pdf", "pdfa", "csv").
	Name string
	From string
	To   string
	// Delimiter and CodePage are the CSV knobs read from LibreOffice's CSV
	// options (0 = not said).
	Delimiter int
	CodePage  int
	// Data is the input's bytes.
	Data []byte
}

OfficeConversion is one file the fake office engine was asked to convert (Host.OfficeFn): what the real host hands the document server.

type OfficeRefusal added in v0.50.0

type OfficeRefusal struct {
	Code int
}

OfficeRefusal is the document server refusing a conversion, with its error code: -7 (it does not make that format from this one), -3 (it could not read the file), -5 (password-protected).

func (*OfficeRefusal) Error added in v0.50.0

func (e *OfficeRefusal) Error() string

type ProgressLine

type ProgressLine struct {
	Done    int64
	Total   int64
	Message string
}

ProgressLine is one job_progress call.

type Report

type Report []Finding

Report is a list of findings.

func InspectJobResult added in v0.50.0

func InspectJobResult(m wire.Manifest, out *wire.ActionRunOutput) Report

InspectJobResult measures a job's answer against what the host does with it. The one part of a job result's `surface` filex acts on is `open`: when the job ends, the person who queued it is sent to one of the files the job produced, with one of the app's own screens on it (a conversion, then the app's next screen on what it wrote). The host DROPS the request, silently for the person, when

  • the path is neither empty (the first output) nor the ref of one of this job's outputs - a job may send its person only to what it made;
  • the job produced nothing to open;
  • the screen is not one of the app's own, or it names both an action and a view.

func InspectLanguages

func InspectLanguages(m wire.Manifest, s *wire.Surface, opts LangOpts) Report

InspectLanguages walks every Text of a surface — the title, the toast, the button labels, the field errors and every Text a node carries — and reports the ones that do not speak every declared language.

func InspectLocaleParity

func InspectLocaleParity(byLocale map[string]*wire.Surface, opts LangOpts) Report

InspectLocaleParity compares the same screen drawn in several languages.

func InspectManifest

func InspectManifest(m wire.Manifest) Report

InspectManifest reads a manifest the way the host's installer does: the name shape, the closed permission set, unique ids, an action whose view exists, a placement filex can draw, and a settings form that obeys the same field rules as a screen.

func InspectManifestLanguages

func InspectManifestLanguages(m wire.Manifest, opts LangOpts) Report

InspectManifestLanguages is CheckManifestLanguages' finding list.

func InspectRegistered

func InspectRegistered(p *pluginkit.Plugin) Report

InspectRegistered is CheckRegistered's finding list.

func InspectSurface

func InspectSurface(m wire.Manifest, s *wire.Surface) Report

InspectSurface measures one screen against the renderer's rules:

  • every node type is in the catalogue;
  • a `select` is a readable choice (it renders as a row of buttons, so its options must be there, labelled and distinct) and `multi` is only on a select;
  • at most ONE primary button — a step asks one thing;
  • `show_when` / `required_when` name a field of the same form;
  • a hidden field's value is not submitted;
  • ids and field keys are unique (values arrive flat).

func (Report) Errors

func (r Report) Errors() Report

Errors is the subset that fails a test.

func (Report) Report

func (r Report) Report(t TB, strict bool)

Report hands the findings to the test: errors fail it, warnings are logged. `strict` turns warnings into failures too.

func (Report) Warnings

func (r Report) Warnings() Report

Warnings is the subset that is only reported.

type Severity

type Severity string

Severity separates "this is broken" from "look at this".

const (
	// SevError fails the test: filex would refuse or mis-draw this.
	SevError Severity = "error"
	// SevWarn is reported but does not fail: it is probably a mistake, and
	// the author is the one who can tell.
	SevWarn Severity = "warning"
)

The two severities.

type Share

type Share struct {
	Token     string
	PageID    string
	Subject   string
	PIN       string
	ExpiresAt time.Time
	MaxVisits int
	Visits    int
	Revoked   bool
	State     json.RawMessage
	// Purpose is what the plugin said this link is (PageCreate.Purpose).
	Purpose *wire.PagePurpose
	// Files are the copies the visitor may read, as `pub:N` refs.
	Files []wire.OutputRef
}

Share is a public link the plugin opened (share_create).

type Signer

type Signer struct {
	Host   *Host
	KeyRef string
	Cert   *x509.Certificate
}

Signer is a crypto.Signer over a key the fake host holds, so a PDF library that takes one (pdfsign, pkcs7) is exercised for real.

func (*Signer) Public

func (s *Signer) Public() crypto.PublicKey

Public returns the certificate's public key.

func (*Signer) Sign

func (s *Signer) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error)

Sign asks the host to sign a sha256 digest.

type TB

type TB interface {
	Helper()
	Errorf(format string, args ...any)
	Fatalf(format string, args ...any)
	Logf(format string, args ...any)
}

TB is the part of *testing.T the kit uses. Taking an interface keeps `testing` out of the plugin's production build.

type TextAt

type TextAt struct {
	Where string
	Text  wire.Text
}

TextAt is one Text and where on the screen it was found.

func SurfaceTexts

func SurfaceTexts(s *wire.Surface, langs []string) []TextAt

SurfaceTexts collects every Text a surface carries, with its path.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL