Files
comic-easel/tests/TranscriptTest.php
T
coraislovely-code 2fe70a3d87 Add characterization tests for the code about to be changed
Security fixes to output escaping carry a specific risk: the fix silently changes what
users see. These tests pin the behaviour that must NOT change, so that any such drift shows
up as a failing assertion rather than as a bug report.

Chosen deliberately to be stable across the fixes that follow, so they stay green
throughout rather than needing to be rewritten by each one. The security assertions -- that
a shortcode attribute cannot reach the SQL string, that a javascript: URI cannot be stored
as a URL -- ship with the commits that make them true, so each fix arrives with the test
that would have caught its absence.

The two most valuable assertions here encode properties rather than values:

- Rendering a transcript must be stable across saves: a value stored the way the save
  handler stores it, through esc_textarea(), must render back as exactly those bytes. The
  case that discriminates is an author who typed a literal entity -- esc_textarea() stores
  "<b>" as "<b>", and an escape that declines to re-encode existing
  entities hands back one level short, so the author's literal text decays into a live tag a
  save at a time.
- The same round-trip property for widget link labels, fed through update() twice.

ArchiveQueryTest uses the $wpdb spy rather than a database, and asserts that table names
come from $wpdb rather than a hardcoded prefix -- a query with the prefix baked in silently
returns nothing on any site that did not accept the installer default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 08:36:46 -04:00

103 lines
3.8 KiB
PHP

<?php
use PHPUnit\Framework\Attributes\DataProvider;
/**
* ceo_the_transcript() — functions/shortcodes.php
*
* Characterization tests. These assert behaviour that holds regardless of how the
* transcript is escaped on output, so they stay green while the escaping work lands.
* The escaping assertions themselves ship with the commit that adds the escaping.
*/
class TranscriptTest extends CE_TestCase {
protected function setUp(): void {
parent::setUp();
self::loadPluginFile( 'functions/shortcodes.php' );
$this->setGlobalPost( 1 );
}
private function withTranscript( $value ) {
$this->setPostMeta( 1, 'transcript', $value );
}
public function testReturnsNullWhenNoTranscriptStored() {
$this->assertNull( ceo_the_transcript( 'raw' ) );
}
/**
* The guard is !empty(), so the string "0" is indistinguishable from no transcript at
* all. Pinned deliberately: it is surprising, it predates this work, and a future
* change to the guard should have to notice it.
*/
public function testTranscriptOfLiteralZeroIsTreatedAsAbsent() {
$this->withTranscript( '0' );
$this->assertNull( ceo_the_transcript( 'raw' ) );
}
/**
* The switch has no default arm, so an unrecognised display mode falls through and the
* function returns nothing. [transcript display=html] therefore renders silently empty.
*/
public function testUnknownDisplayModeReturnsNull() {
$this->withTranscript( 'Panel one.' );
$this->assertNull( ceo_the_transcript( 'html' ) );
}
public function testRawModeReturnsTheTranscript() {
$this->withTranscript( "Panel one.\nPanel two." );
$this->assertStringContainsString( 'Panel one.', ceo_the_transcript( 'raw' ) );
$this->assertStringContainsString( 'Panel two.', ceo_the_transcript( 'raw' ) );
}
/**
* nl2br() is applied after whatever escaping is in force, so the <br /> must be live
* markup in the output rather than an escaped literal.
*/
public function testBrModeConvertsNewlinesToLiveMarkup() {
$this->withTranscript( "one\ntwo" );
$out = ceo_the_transcript( 'br' );
$this->assertStringContainsString( '<br />', $out );
}
public function testStyledModeWrapsTranscriptInTheExpanderMarkup() {
$this->withTranscript( 'Panel one.' );
$out = ceo_the_transcript( 'styled' );
$this->assertStringContainsString( 'transcript-border', $out );
$this->assertStringContainsString( 'transcript-content', $out );
$this->assertStringContainsString( 'Panel one.', $out );
}
/**
* Display must be stable: a transcript stored the way the plugin's save handler stores
* it -- through esc_textarea() -- must render back as exactly those bytes, so that what
* the author typed is what the reader sees, on this render and every later one.
*
* The case that matters is an author who typed a literal entity. esc_textarea() stores
* "&lt;b&gt;" as "&amp;lt;b&amp;gt;". An escape that declines to re-encode existing
* entities -- which is what esc_html() does, since it passes $double_encode = false --
* gives back "&lt;b&gt;" instead, one level short. Repeat that on each save and the
* author's literal text decays into a live <b> tag.
*/
#[DataProvider( 'authoredTextProvider' )]
public function testRenderingATranscriptIsStableAcrossSaves( $typed ) {
$stored = esc_textarea( $typed );
$this->withTranscript( $stored );
$this->assertSame(
$stored,
ceo_the_transcript( 'raw' ),
'Rendering must return the stored bytes unchanged, or encoding is lost on each save'
);
}
public static function authoredTextProvider() {
return array(
'markup the author wants shown as text' => array( '<b>bold</b>' ),
'an entity the author typed literally' => array( '&lt;b&gt;' ),
'a bare ampersand' => array( 'Tom & Jerry' ),
'an ampersand the author escaped' => array( 'Tom &amp; Jerry' ),
'quotes' => array( 'she said "hi"' ),
);
}
}