Skip to content

Commit e9c381e

Browse files
authored
Merge pull request #4 from imbus/test/improve_advanced_search-pipeline
Test/improve advanced search pipeline
2 parents a5335d9 + efcc626 commit e9c381e

File tree

4 files changed

+435
-0
lines changed

4 files changed

+435
-0
lines changed

.github/workflows/imbus_ci.yml

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
name: imbus_ci
2+
3+
on:
4+
push:
5+
branches: ['develope, master']
6+
pull_request:
7+
workflow_dispatch:
8+
9+
concurrency:
10+
group: imbus-main-ci-${{ github.ref }}
11+
cancel-in-progress: true
12+
13+
jobs:
14+
tests-mysql:
15+
name: MySQL • PHP ${{ matrix.php }}
16+
runs-on: ubuntu-latest
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
php: ['8.2','8.3','8.4']
21+
22+
services:
23+
mysql:
24+
image: mysql:5.7
25+
env:
26+
MYSQL_ALLOW_EMPTY_PASSWORD: yes
27+
MYSQL_DATABASE: snipeit_test
28+
ports:
29+
- 33306:3306
30+
options:
31+
--health-cmd="mysqladmin ping"
32+
--health-interval=10s --health-timeout=5s --health-retries=5
33+
34+
steps:
35+
- uses: actions/checkout@v4
36+
37+
- name: Setup PHP
38+
uses: shivammathur/setup-php@v2
39+
with:
40+
php-version: ${{ matrix.php }}
41+
coverage: none
42+
tools: composer
43+
44+
- name: Cache Composer
45+
id: composer-cache
46+
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
47+
- uses: actions/cache@v4
48+
with:
49+
path: ${{ steps.composer-cache.outputs.dir }}
50+
key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }}
51+
restore-keys: |
52+
${{ runner.os }}-${{ matrix.php }}-composer-
53+
54+
- name: Prepare .env.testing (MySQL)
55+
run: |
56+
cp -v .env.testing-ci .env.testing || cp -v .env.testing.example .env.testing
57+
sed -i 's/^APP_ENV=.*/APP_ENV=testing/' .env.testing || echo "APP_ENV=testing" >> .env.testing
58+
sed -i 's/^DB_CONNECTION=.*/DB_CONNECTION=mysql/' .env.testing
59+
sed -i 's/^DB_HOST=.*/DB_HOST=127.0.0.1/' .env.testing
60+
sed -i 's/^DB_PORT=.*/DB_PORT=${{ job.services.mysql.ports[3306] }}/' .env.testing
61+
sed -i 's/^DB_DATABASE=.*/DB_DATABASE=snipeit_test/' .env.testing
62+
sed -i 's/^DB_USERNAME=.*/DB_USERNAME=root/' .env.testing
63+
sed -i 's/^DB_PASSWORD=.*/DB_PASSWORD=/' .env.testing
64+
65+
- name: Install Dependencies
66+
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
67+
68+
- name: Migrate & Prepare
69+
env:
70+
APP_ENV: testing
71+
run: |
72+
php artisan key:generate --env=testing
73+
php artisan config:clear
74+
php artisan migrate --env=testing --force
75+
php artisan passport:install --env=testing --no-interaction || true
76+
chmod -R 777 storage bootstrap/cache
77+
78+
- name: Run Tests (MySQL)
79+
env:
80+
APP_ENV: testing
81+
run: php artisan test --env=testing
82+
83+
tests-sqlite:
84+
name: SQLite • PHP ${{ matrix.php }}
85+
runs-on: ubuntu-latest
86+
strategy:
87+
fail-fast: false
88+
matrix:
89+
php: ['8.2','8.3','8.4']
90+
91+
steps:
92+
- uses: actions/checkout@v4
93+
94+
- name: Setup PHP
95+
uses: shivammathur/setup-php@v2
96+
with:
97+
php-version: ${{ matrix.php }}
98+
coverage: none
99+
tools: composer, sqlite
100+
101+
- name: Cache Composer
102+
id: composer-cache
103+
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
104+
- uses: actions/cache@v4
105+
with:
106+
path: ${{ steps.composer-cache.outputs.dir }}
107+
key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }}
108+
restore-keys: |
109+
${{ runner.os }}-${{ matrix.php }}-composer-
110+
111+
- name: Prepare .env.testing (SQLite)
112+
run: |
113+
cp -v .env.testing-ci .env.testing || cp -v .env.testing.example .env.testing
114+
sed -i 's/^APP_ENV=.*/APP_ENV=testing/' .env.testing || echo "APP_ENV=testing" >> .env.testing
115+
sed -i 's/^DB_CONNECTION=.*/DB_CONNECTION=sqlite/' .env.testing || echo "DB_CONNECTION=sqlite" >> .env.testing
116+
sed -i 's|^DB_DATABASE=.*|DB_DATABASE=database/testing.sqlite|' .env.testing || echo "DB_DATABASE=database/testing.sqlite" >> .env.testing
117+
mkdir -p database && touch database/testing.sqlite
118+
119+
- name: Install Dependencies
120+
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
121+
122+
- name: Migrate & Prepare
123+
env:
124+
APP_ENV: testing
125+
run: |
126+
php artisan key:generate --env=testing
127+
php artisan config:clear
128+
php artisan migrate --env=testing --force
129+
php artisan passport:install --env=testing --no-interaction || true
130+
chmod -R 777 storage bootstrap/cache
131+
132+
- name: Run Tests (SQLite)
133+
env:
134+
APP_ENV: testing
135+
run: php artisan test --env=testing

tests/Feature/AssetQuery/AssignedToQueryTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public function testFilterAssetAssignedToUserIdArray()
5050
$userB = User::factory()->create();
5151
$userC = User::factory()->create();
5252

53+
// Assets
5354
$assetA = Asset::factory()->create(['assigned_type' => User::class, 'assigned_to' => $userA->id]);
5455
$assetB = Asset::factory()->create(['assigned_type' => User::class, 'assigned_to' => $userB->id]);
5556
$assetC = Asset::factory()->create(['assigned_type' => User::class, 'assigned_to' => $userC->id]);
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
<?php
2+
namespace Tests\Unit;
3+
4+
use App\Models\Asset;
5+
use Illuminate\Database\Schema\Blueprint;
6+
use Illuminate\Support\Facades\Schema;
7+
use Tests\TestCase;
8+
9+
class CustomFieldQueryTest extends TestCase
10+
{
11+
protected function setUp(): void
12+
{
13+
parent::setUp();
14+
15+
Schema::table('assets', function (Blueprint $table) {
16+
if (!Schema::hasColumn('assets', 'custom_text')) {
17+
$table->string('custom_text')->nullable()->index();
18+
}
19+
if (!Schema::hasColumn('assets', 'custom_flag')) {
20+
$table->string('custom_flag')->nullable()->index();
21+
}
22+
if (!Schema::hasColumn('assets', 'custom_code')) {
23+
$table->string('custom_code')->nullable()->index();
24+
}
25+
});
26+
}
27+
28+
public function testFilterBySingleCustomFieldStringLike()
29+
{
30+
$aMatch = Asset::factory()->create(['custom_text' => 'Alpha Blue']);
31+
$aNoMatch1 = Asset::factory()->create(['custom_text' => 'Gamma Green']);
32+
$aNoMatch2 = Asset::factory()->create(['custom_text' => 'Delta Red']);
33+
34+
$filter = ['custom_fields.custom_text' => 'Blu'];
35+
$results = Asset::query()->byFilter($filter)->get();
36+
37+
$this->assertCount(1, $results);
38+
$this->assertTrue($results->contains($aMatch));
39+
$this->assertFalse($results->contains($aNoMatch1));
40+
$this->assertFalse($results->contains($aNoMatch2));
41+
}
42+
43+
public function testFilterBySingleCustomFieldArrayWhereIn()
44+
{
45+
$a1 = Asset::factory()->create(['custom_text' => 'ValueA']);
46+
$a2 = Asset::factory()->create(['custom_text' => 'ValueB']);
47+
$a3 = Asset::factory()->create(['custom_text' => 'ValueC']);
48+
49+
$filter = ['custom_fields.custom_text' => ['ValueA', 'ValueB']];
50+
$results = Asset::query()->byFilter($filter)->get();
51+
52+
$this->assertCount(2, $results);
53+
$this->assertTrue($results->contains($a1));
54+
$this->assertTrue($results->contains($a2));
55+
$this->assertFalse($results->contains($a3));
56+
}
57+
58+
public function testFilterBooleanLikeCustomFieldArrayAndString()
59+
{
60+
$on = Asset::factory()->create(['custom_flag' => '1']);
61+
$off = Asset::factory()->create(['custom_flag' => '0']);
62+
63+
$resIn = Asset::query()->byFilter(['custom_fields.custom_flag' => ['1']])->get();
64+
$this->assertCount(1, $resIn);
65+
$this->assertTrue($resIn->contains($on));
66+
$this->assertFalse($resIn->contains($off));
67+
68+
$resLike = Asset::query()->byFilter(['custom_fields.custom_flag' => '1'])->get();
69+
$this->assertCount(1, $resLike);
70+
$this->assertTrue($resLike->contains($on));
71+
$this->assertFalse($resLike->contains($off));
72+
}
73+
74+
public function testFilterMultipleCustomFieldsCombined()
75+
{
76+
77+
$hit = Asset::factory()->create(['custom_text' => 'Report Q3', 'custom_code' => 'R-2025']);
78+
$missText = Asset::factory()->create(['custom_text' => 'Notes Q3', 'custom_code' => 'R-2025']);
79+
$missCode = Asset::factory()->create(['custom_text' => 'Report Q3', 'custom_code' => 'X-0001']);
80+
81+
$filter = [
82+
'custom_fields.custom_text' => 'Report',
83+
'custom_fields.custom_code' => 'R-2025',
84+
];
85+
86+
$results = Asset::query()->byFilter($filter)->get();
87+
88+
$this->assertCount(1, $results);
89+
$this->assertTrue($results->contains($hit));
90+
$this->assertFalse($results->contains($missText));
91+
$this->assertFalse($results->contains($missCode));
92+
}
93+
94+
public function testFilterWithEmptyArrayLeavesResultsUnchanged()
95+
{
96+
$a = Asset::factory()->create(['custom_text' => 'A']);
97+
$b = Asset::factory()->create(['custom_text' => 'B']);
98+
99+
$filter = ['custom_fields.custom_text' => []];
100+
$results = Asset::query()->byFilter($filter)->get();
101+
102+
$this->assertCount(0, $results);
103+
$this->assertFalse($results->contains($a));
104+
$this->assertFalse($results->contains($b));
105+
}
106+
107+
public function testFilterWithNonexistentValueReturnsNone()
108+
{
109+
Asset::factory()->count(3)->create(['custom_text' => 'X']);
110+
$filter = ['custom_fields.custom_text' => 'does-not-exist'];
111+
$results = Asset::query()->byFilter($filter)->get();
112+
113+
$this->assertCount(0, $results);
114+
}
115+
116+
public function testFilterCombinationArrayAndStringAcrossCustomFields()
117+
{
118+
$keep1 = Asset::factory()->create(['custom_text' => 'Alpha', 'custom_code' => 'G-100']);
119+
$keep2 = Asset::factory()->create(['custom_text' => 'Beta', 'custom_code' => 'G-100']);
120+
$drop = Asset::factory()->create(['custom_text' => 'Gamma', 'custom_code' => 'Z-999']);
121+
122+
$filter = [
123+
'custom_fields.custom_text' => ['Alpha', 'Beta'],
124+
'custom_fields.custom_code' => 'G-100',
125+
];
126+
127+
$results = Asset::query()->byFilter($filter)->get();
128+
129+
$this->assertCount(2, $results);
130+
$this->assertTrue($results->contains($keep1));
131+
$this->assertTrue($results->contains($keep2));
132+
$this->assertFalse($results->contains($drop));
133+
}
134+
135+
public function testFilterWithSpecialCharactersInCustomField()
136+
{
137+
$match = Asset::factory()->create(['custom_text' => 'Mödel#1 (ß)']);
138+
$nope = Asset::factory()->create(['custom_text' => 'Model 2']);
139+
140+
$filter = ['custom_fields.custom_text' => 'Mödel#1'];
141+
$results = Asset::query()->byFilter($filter)->get();
142+
143+
$this->assertCount(1, $results);
144+
$this->assertTrue($results->contains($match));
145+
$this->assertFalse($results->contains($nope));
146+
}
147+
}

0 commit comments

Comments
 (0)