Developer Tests Knowledge - edmozley/freeitsm GitHub Wiki
π§ͺ Developer Tests β Knowledge, search & documents
Part of Developer Tests. These are the suites where a wrong answer looks like a right one, so nearly every assertion here is paired with a positive control.
That is the theme of the whole page and worth stating once: a permission filter that quietly does nothing returns a page full of plausible results, and a query that matches nothing looks identical to an honest "not in your tickets". So every "they could not see it" is followed by "β¦and this person, who should, still can". Without the second half the first proves nothing.
| Test | Needs |
|---|---|
knowledge-visibility/run.php |
Database, some parts local HTTP |
knowledge-gaps/run.php |
Database (rolled back) |
search/run.php |
Database |
search-extract/run.php |
Nothing |
document-permissions.php |
Database (rolled back) |
document-search-permissions.php |
Database |
knowledge-help-centre-documents.php |
Database |
tests/knowledge-visibility/run.php
What it tests
Whether one company's knowledge can reach another company's people. It is a runner: it executes the seven numbered harnesses beside it in order and reports which failed.
| Part | The question it asks |
|---|---|
01_webchat_scope.php |
Can one company's anonymous website visitor be answered out of another company's knowledge base? |
02_write_path.php |
Does KnowledgeService validate company and audience when writing? |
03_readers.php |
Does every analyst-facing surface respect the company the analyst has switched to? |
04_rest_api.php |
Does an API key scoped to one company see only that company's articles β but still the shared ones? |
05_lms.php |
Can the LMS build a lesson from an article it should not have? |
06_gap_analysis.php |
Does the assistant judge one company's tickets "already covered" by another's articles? |
07_acl.php |
Folders, the access list, and the two permission models. |
How the interesting ones work
03_readers.php drives the real endpoints over HTTP with a forged session, as
a restricted analyst. That is the point β a scoped query proves nothing if the
endpoint in front of it never applies the scope.
04_rest_api.php has a trap worth knowing about. The second half is the hard
part: the generic apiKeyTenantFilter treats NULL as "the Default company's",
which would hide every shared article from a non-Default key. So the test
asserts both directions β not another company's, but yes to the shared ones.
05_lms.php makes the same argument the Forms audience test does: the picker
hiding a title is not enough, because it posts an id, and ai_author.php is gated
on LMS_MANAGE rather than on Knowledge. The real test is whether a guessed id
still yields a body.
06_gap_analysis.php documents a mismatch precisely: gapWindowSql() always
company-scoped the ticket side, while the article side had no tenant filter
at all β the two halves of one comparison disagreeing about whose data was in
scope.
07_acl.php runs inside one transaction that is always rolled back, so it
is safe against a live database.
_bootstrap.php beside them is a shared helper, not a harness β it is included by
each part and has no assertions of its own.
Run it
php tests/knowledge-visibility/run.php
Or one part on its own, e.g. php tests/knowledge-visibility/07_acl.php.
β οΈ Several parts look for tenants by name on your database. They are written against a specific development install, so on a different database they may not find what they expect β read the output rather than assuming a clean pass.
Reading the result
All Knowledge visibility harnesses passed. and exit 0. Otherwise it prints
FAILED: and the harness names, and exits 1.
If it fails
Any leak here is a live cross-company data leak. Fix it at the query, not at the surface that happened to be tested β the reason there are seven harnesses is that there are seven ways in.
tests/knowledge-gaps/run.php
What it tests
The knowledge gap assistant's clustering. The interesting behaviour is emergent: does a pile of real tickets actually collapse into the right questions, and do the one-offs stay out? You cannot answer that by reading the code β only by feeding it a pile whose right answer you already know.
How it works
Everything runs inside one transaction which is always rolled back, so it is safe against a live database.
It runs in "wording" mode by default, which needs no OpenAI key and spends nothing. The clustering logic under test is identical; only the similarity function differs.
β οΈ Every negative assertion is paired with a positive control. "The one-offs did not cluster" proves nothing on its own β it is equally true of a harness that silently clustered nothing at all.
Run it
php tests/knowledge-gaps/run.php
If it reports the write-up schema is not ready, run System β Database Verification.
If it fails
Clustering has changed shape. Check the control assertions first: if those failed, nothing clustered and the tuning is broken rather than merely different.
tests/search/run.php
What it tests
Three things, all of which can be wrong while looking right:
- Query translation. What a person types is not what MySQL is asked. Terms too short to be indexed must be dropped, because requiring one in boolean mode makes the whole query match nothing β the difference between "search works" and "search mysteriously returns nothing for that phrase".
- Scope goes into the query. Every "it was excluded" assertion has a twin showing the same row is returned once the predicate is relaxed. Without that twin, a filter matching nothing at all would pass.
- The result shape. Hits collapse to their ticket and report which parts matched, because a user thinks in tickets β one ticket with the term in four replies must not flood the page.
How it works
Writes a handful of rows with a reserved source_type and deletes them in a
finally block. β οΈ It cannot use a rolled-back transaction: InnoDB does not
expose uncommitted rows to MATCH ... AGAINST.
Run it
php tests/search/run.php
If it fails
- Translation β a search phrase now returns nothing. Look at the minimum indexed word length and what the translator does with short terms.
- A scope twin β if the exclusion passes but its twin fails, the filter is excluding everything, not just what it should.
tests/search-extract/run.php
What it tests
Attachment text extraction: reading words out of an uploaded file so they can be searched.
How it works
Builds real files in a temporary directory and reads them back β including a
minimal but genuinely valid .docx, which is a zip with word/document.xml
inside. No database, no HTTP: it exercises includes/search/extract.php alone,
because the risky part of attachment indexing is the file handling, not the
SQL.
π The hostile cases matter most. An attachment arrives from anyone who can email the service desk, so "a zip bomb is refused" is a more useful assertion than "a Word document is read".
Run it
php tests/search-extract/run.php
25 assertions, no fixtures needed β this is a good first test to run on a fresh checkout.
If it fails
A refused-hostile-file assertion going red means the extractor will now try to process something it should reject. That is a denial-of-service risk on a queue anyone can post to.
tests/document-permissions.php
What it tests
A document is visible if and only if you can see something it is attached to.
π The same question is asked twice by the product, and the two must always agree:
documentVisibilityClause()β the set, used by searchdocumentCanView()β the row, used by download
A document you cannot find but can download is the whole bug this design exists to prevent.
How it works
Writes to scratch records inside a transaction and rolls back. Every "cannot see" is followed by the same analyst being granted the parent and then seeing the document.
Run it
php tests/document-permissions.php
If it fails
If the set and the row disagree, say so explicitly in your fix β decide which is right and change the other. Do not fix only the one the test named.
tests/document-search-permissions.php
What it tests
That search does not return a document you cannot see.
β οΈ This is the one that matters. A corpus row carries a tenant and an internal flag, which is enough to judge a ticket. It is not enough to judge a document, whose visibility lives in other tables entirely β so a document row has to satisfy the same at-least-one-visible-parent rule the download endpoint applies.
How it works
Searches the real corpus as a real analyst. Every denial is paired with a positive control: the same analyst, the same document, the module granted. A test that only proves absence passes just as happily when the index is empty, the word is below the full-text minimum, or the query is broken.
Run it
php tests/document-search-permissions.php
If it fails
A denial going red is a leak through search β someone can see the existence and title of a document they have no route to. Treat as blocking. A control going red means documents have stopped being indexed at all.
tests/knowledge-help-centre-documents.php
What it tests
Which documents a self-service requester can download from the Help Centre (#177, 3.7.0). The rule: a document on a knowledge article is downloadable by anyone who may read that article in the Help Centre, unless that attachment is Hidden.
- Positive controls first: a shown document on a customer article downloads, byte for byte; so does one on a public article, and (multi-company) one on the requester's own company's article.
- Every refusal: the same document set to Hidden; an internal, unpublished or archived article; another company's article; a document only on a contract even with the flag set by hand; an id that does not exist.
- Two pins: one file hidden on a readable article and shown on an unreadable one is refused; show it on the readable one and it downloads; the other pin is untouched.
- The article page lists exactly its shown documents; setting the article back to analysts only takes its documents out with it.
- A requester marked as left fails the endpoint's first check.
How it works
Uses the functions the portal endpoints call - helpCentreDocumentForPortalUser() decides get_article_document.php, helpCentreArticleDocuments() fills get_knowledge_article.php - against throwaway rows named zz-test-hc (a portal requester, articles, documents, links and real files in document storage), deleted by id in a finally and checked gone.
Before Database Verification has added document_links.show_in_help_centre, it runs a smaller set instead: a document still attaches to an article, and the Help Centre lists and serves nothing. Every upgraded install is in that state until verification runs, so it is tested rather than assumed.
Run it
php tests/knowledge-help-centre-documents.php
If it fails
A refused check going red is a leak: customers can download something they should not. Treat it as blocking a release. A positive control going red means the feature has stopped working, usually because "may this requester read this article" no longer comes from portalCanReadArticle().