Passed
Branch master (a3c4c5)
by Tim
02:38 queued 53s
created

SchemaValidationTestTrait::validateDocument()   A

Complexity

Conditions 6
Paths 9

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 17
c 2
b 0
f 0
dl 0
loc 29
rs 9.0777
cc 6
nc 9
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XML\TestUtils;
6
7
use DOMDocument;
8
use Exception;
9
use LibXMLError; // Officially spelled with a lower-case `l`, but that breaks composer-require-checker
10
use PHPUnit\Framework\Attributes\Depends;
11
use SimpleSAML\Assert\Assert;
12
use SimpleSAML\XML\Exception\SchemaViolationException;
13
use XMLReader;
14
15
use function array_unique;
16
use function class_exists;
17
use function implode;
18
use function libxml_get_last_error;
19
use function libxml_use_internal_errors;
20
use function trim;
21
22
/**
23
 * Test for AbstractElement classes to perform schema validation tests.
24
 *
25
 * @package simplesamlphp\xml-common
26
 */
27
trait SchemaValidationTestTrait
28
{
29
    /** @var class-string */
30
    protected static string $testedClass;
31
32
    /** @var string */
33
    protected static string $schemaFile;
34
35
    /** @var \DOMDocument */
36
    protected static DOMDocument $xmlRepresentation;
37
38
39
    /**
40
     * Test schema validation.
41
     */
42
    #[Depends('testSerialization')]
43
    public function testSchemaValidation(): void
44
    {
45
        if (!class_exists(self::$testedClass)) {
46
            $this->markTestSkipped(
47
                'Unable to run ' . self::class . '::testSchemaValidation(). Please set ' . self::class
48
                . ':$testedClass to a class-string representing the XML-class being tested',
49
            );
50
        } elseif (empty(self::$schemaFile)) {
51
            $this->markTestSkipped(
52
                'Unable to run ' . self::class . '::testSchemaValidation(). Please set ' . self::class
53
                . ':$schema to point to a schema file',
54
            );
55
        } elseif (empty(self::$xmlRepresentation)) {
56
            $this->markTestSkipped(
57
                'Unable to run ' . self::class . '::testSchemaValidation(). Please set ' . self::class
58
                . ':$xmlRepresentation to a DOMDocument representing the XML-class being tested',
59
            );
60
        } else {
61
            $predoc = XMLReader::XML(self::$xmlRepresentation->saveXML());
62
            Assert::notFalse($predoc);
63
64
            $pre = $this->validateDocument($predoc);
65
            $this->assertTrue($pre);
66
67
            $class = self::$testedClass::fromXML(self::$xmlRepresentation->documentElement);
68
            $serializedClass = $class->toXML();
69
70
            $postdoc = XMLReader::XML($serializedClass->ownerDocument->saveXML());
71
            Assert::notFalse($postdoc);
72
            $post = $this->validateDocument($postdoc);
73
            $this->assertTrue($post);
74
        }
75
    }
76
77
78
    /**
79
     * @param \XMLReader $doc
80
     * @return boolean
81
     */
82
    private function validateDocument(XMLReader $xmlReader): bool
83
    {
84
        libxml_use_internal_errors(true);
85
86
        try {
87
            $xmlReader->setSchema(self::$schemaFile);
88
        } catch (Exception) {
89
            $err = libxml_get_last_error();
90
            throw new SchemaViolationException(trim($err->message) . ' on line ' . $err->line);
91
        }
92
93
        $msgs = [];
94
        while ($xmlReader->read()) {
95
            if (!$xmlReader->isValid()) {
96
                $err = libxml_get_last_error();
97
                if ($err instanceof LibXMLError) {
98
                    $msgs[] = trim($err->message) . ' on line ' . $err->line;
99
                }
100
            }
101
        }
102
103
        if ($msgs) {
104
            throw new SchemaViolationException(sprintf(
105
                "XML schema validation errors:\n - %s",
106
                implode("\n - ", array_unique($msgs))
107
            ));
108
        }
109
110
        return true;
111
    }
112
113
114
    abstract public function testSerialization(): void;
115
}
116