From 0b974869bad27bc7c1082883fa349f6bf3203596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20FRANCE?= Date: Fri, 14 Aug 2026 14:57:48 +0200 Subject: [PATCH] Add opensurveysondage phpunit test (#39522) * FIX pre-commit PHPStan hook errors on commits with no file in scope phpstan.neon.dist only analyzes htdocs/ and scripts/, but the pre-commit wrapper passed every staged file straight to phpstan regardless of path. A commit touching only files outside that scope (e.g. test/phpunit/) made phpstan exit with "No files found to analyse", failing the hook for reasons unrelated to the change being committed. Filter to htdocs/ and scripts/ first, and skip cleanly when nothing remains. * NEW: Add phpunit test for Opensurveysondage class Add a CRUD test (create/fetch/update/delete) for the Opensurveysondage class, which had no test coverage yet. Note: create()'s return value is not used to identify the created record - Opensurveysondage::create() returns $this->id, but this class never sets $this->id (the real primary key is the caller- supplied $this->id_sondage string), so the return value carries no information. The test uses errors/re-fetch to check success instead. * NEW: Auto-activate module opensurvey in OpensurveysondageTest if needed Activate module opensurvey in setUpBeforeClass() when it is not already enabled, so the test does not depend on the environment's module configuration. Note: this activation is real and persists after the test run - it is not undone by the transaction rollback in tearDownAfterClass(). Activating a module re-runs its SQL install scripts (CREATE/ALTER TABLE), which causes an implicit commit in MySQL/InnoDB, same as an admin enabling it from Setup > Modules would do. --- .../phpstan/allow_phpstan_in_precommit.sh | 19 +- test/phpunit/AllTests.php | 3 + test/phpunit/OpensurveysondageTest.php | 203 ++++++++++++++++++ 3 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 test/phpunit/OpensurveysondageTest.php diff --git a/dev/tools/phpstan/allow_phpstan_in_precommit.sh b/dev/tools/phpstan/allow_phpstan_in_precommit.sh index 3ab58d99bcf..2271630d78d 100755 --- a/dev/tools/phpstan/allow_phpstan_in_precommit.sh +++ b/dev/tools/phpstan/allow_phpstan_in_precommit.sh @@ -1,4 +1,6 @@ #!/bin/bash +# Copyright (C) 2026 Frédéric France + # Wrapper to run 'PHPStan' from pre-commit hook # This is very slow so not enabled by default # To enable it, create a file ~/.run-phpstan @@ -17,6 +19,21 @@ if [ ! -f ~/vendor/bin/phpstan ]; then exit 0 fi -~/vendor/bin/phpstan --level=9 -v analyze -a dev/build/phpstan/bootstrap.php $@ +# phpstan.neon.dist only analyzes htdocs/ and scripts/: keep only files under these dirs so a commit +# that touches only out-of-scope files (test/phpunit/, dev/, doc/, ...) does not make phpstan error +# out with "No files found to analyse". +filtered=() +for f in "$@"; do + case "$f" in + htdocs/*|scripts/*) filtered+=("$f") ;; + esac +done + +if [ ${#filtered[@]} -eq 0 ]; then + echo "Skipping PHPStan (no file in scope: htdocs/ or scripts/)" + exit 0 +fi + +~/vendor/bin/phpstan --level=9 -v analyze -a dev/build/phpstan/bootstrap.php "${filtered[@]}" exit $? diff --git a/test/phpunit/AllTests.php b/test/phpunit/AllTests.php index 7113ba2363e..d8fb0f5be70 100644 --- a/test/phpunit/AllTests.php +++ b/test/phpunit/AllTests.php @@ -197,6 +197,9 @@ class AllTests require_once dirname(__FILE__).'/BOMTest.php'; $suite->addTestSuite('BOMTest'); + require_once dirname(__FILE__).'/OpensurveysondageTest.php'; + $suite->addTestSuite('OpensurveysondageTest'); + require_once dirname(__FILE__).'/ContratTest.php'; $suite->addTestSuite('ContratTest'); diff --git a/test/phpunit/OpensurveysondageTest.php b/test/phpunit/OpensurveysondageTest.php new file mode 100644 index 00000000000..d488373ec86 --- /dev/null +++ b/test/phpunit/OpensurveysondageTest.php @@ -0,0 +1,203 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * or see https://www.gnu.org/ + */ + +/** + * \file test/phpunit/OpensurveysondageTest.php + * \ingroup test + * \brief PHPUnit test + * \remarks To run this script as CLI: phpunit filename.php + */ + +global $conf,$user,$langs,$db; +require_once dirname(__FILE__).'/../../htdocs/master.inc.php'; +require_once dirname(__FILE__).'/../../htdocs/opensurvey/class/opensurveysondage.class.php'; +require_once dirname(__FILE__).'/../../htdocs/opensurvey/lib/opensurvey.lib.php'; +require_once dirname(__FILE__).'/CommonClassTest.class.php'; + +if (empty($user->id)) { + print "Load permissions for admin user nb 1\n"; + $user->fetch(1); + $user->loadRights(); +} +$conf->global->MAIN_DISABLE_ALL_MAILS = 1; + + +/** + * Class for PHPUnit tests + * + * @backupGlobals disabled + * @backupStaticAttributes enabled + * @remarks backupGlobals must be disabled to have db,conf,user and lang not erased. + */ +class OpensurveysondageTest extends CommonClassTest +{ + /** + * setUpBeforeClass + * + * @return void + */ + public static function setUpBeforeClass(): void + { + global $db, $conf; + + if (!isModEnabled('opensurvey')) { + // Activating a module re-runs its SQL install scripts (CREATE/ALTER TABLE), which causes an + // implicit commit in MySQL/InnoDB: this activation is real and is NOT undone by the + // rollback in tearDownAfterClass, exactly like an admin enabling it from Setup > Modules + // would be (see also FactureTest::setUpBeforeClass(), which similarly disables the + // blockedlog module for real, outside of any transaction). Do this before starting the + // test transaction below, so the transaction-open counter stays consistent. + require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; + $result = activateModule('modOpenSurvey'); + self::assertEmpty($result['errors'], 'Failed to activate module opensurvey: '.implode(', ', $result['errors'])); + $conf->setValues($db); + } + + self::assertTrue(isModEnabled('opensurvey'), 'module opensurvey must be enabled'); + + parent::setUpBeforeClass(); + } + + /** + * testOpensurveysondageCreate + * + * The primary key (id_sondage) is a random string chosen by the caller before create(), like + * the real usage in opensurvey/lib/opensurvey.lib.php::ajouter_sondage(). It is used instead of + * the create() return value to identify the record: Opensurveysondage::create() returns + * $this->id, but $this->id is never set by this class (the real key is $this->id_sondage), so + * the return value is always null on success. + * + * @return Opensurveysondage + */ + public function testOpensurveysondageCreate() + { + global $conf,$user,$langs,$db; + $conf = $this->savconf; + $user = $this->savuser; + $langs = $this->savlangs; + $db = $this->savdb; + + $localobject = new Opensurveysondage($db); + $localobject->initAsSpecimen(); + $localobject->id_sondage = dol_survey_random(16); + // initAsSpecimen() sets format='classic', but the column is varchar(2) and the only documented + // valid values are 'A' (text choice), 'D' (date choice) or 'F' (form) - use a valid one here. + $localobject->format = 'A'; + $result = $localobject->create($user); + + print __METHOD__." id_sondage=".$localobject->id_sondage." result=".var_export($result, true)."\n"; + $this->assertEmpty($localobject->errors, $localobject->errorsToString()); + + // Confirm the record was really created (create()'s return value is not reliable, see above) + $checkobject = new Opensurveysondage($db); + $this->assertGreaterThan(0, $checkobject->fetch(0, $localobject->id_sondage)); + + return $localobject; + } + + /** + * testOpensurveysondageFetch + * + * @param Opensurveysondage $localobject Poll created by the previous test + * @return Opensurveysondage + * + * @depends testOpensurveysondageCreate + * The depends says test is run only if previous is ok + */ + public function testOpensurveysondageFetch($localobject) + { + global $conf,$user,$langs,$db; + $conf = $this->savconf; + $user = $this->savuser; + $langs = $this->savlangs; + $db = $this->savdb; + + $fetched = new Opensurveysondage($db); + $result = $fetched->fetch(0, $localobject->id_sondage); + + $this->assertGreaterThan(0, $result, $fetched->errorsToString()); + print __METHOD__." id_sondage=".$localobject->id_sondage." result=".$result."\n"; + + $this->assertSame($localobject->id_sondage, $fetched->id_sondage); + $this->assertSame('This is a specimen survey', $fetched->title); + $this->assertSame('Description of the specimen survey', $fetched->description); + $this->assertSame('A', $fetched->format); + $this->assertEquals(Opensurveysondage::STATUS_VALIDATED, $fetched->status); + + return $fetched; + } + + /** + * testOpensurveysondageUpdate + * + * @param Opensurveysondage $localobject Poll + * @return Opensurveysondage + * + * @depends testOpensurveysondageFetch + * The depends says test is run only if previous is ok + */ + public function testOpensurveysondageUpdate($localobject) + { + global $conf,$user,$langs,$db; + $conf = $this->savconf; + $user = $this->savuser; + $langs = $this->savlangs; + $db = $this->savdb; + + $localobject->title = 'Updated title after update'; + $localobject->description = 'Updated description after update'; + $result = $localobject->update($user); + + $this->assertGreaterThan(0, $result, $localobject->errorsToString()); + print __METHOD__." id_sondage=".$localobject->id_sondage." result=".$result."\n"; + + $localobject->fetch(0, $localobject->id_sondage); + $this->assertSame('Updated title after update', $localobject->title); + $this->assertSame('Updated description after update', $localobject->description); + + return $localobject; + } + + /** + * testOpensurveysondageDelete + * + * @param Opensurveysondage $localobject Poll + * @return int + * + * @depends testOpensurveysondageUpdate + * The depends says test is run only if previous is ok + */ + public function testOpensurveysondageDelete($localobject) + { + global $conf,$user,$langs,$db; + $conf = $this->savconf; + $user = $this->savuser; + $langs = $this->savlangs; + $db = $this->savdb; + + $result = $localobject->delete($user); + + $this->assertGreaterThan(0, $result, $localobject->errorsToString()); + print __METHOD__." id_sondage=".$localobject->id_sondage." result=".$result."\n"; + + $checkobject = new Opensurveysondage($db); + $this->assertSame(0, $checkobject->fetch(0, $localobject->id_sondage), 'Poll must no longer be found after delete'); + + return $result; + } +}