chore: add tooling config, test bootstrap, env templates and docs
PHPUnit config (phpunit.dist.xml, bin/phpunit, bootstrap.php), PHP CS Fixer config, .editorconfig. Separate .env.dev/.env.test templates. Ollama tunnel setup script. Architecture and plan docs. Updated application-layer unit tests to match current service signatures. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2cfc5e8f17
commit
f55e96b094
21 changed files with 12348 additions and 25 deletions
17
.editorconfig
Normal file
17
.editorconfig
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[{compose.yaml,compose.*.yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
4
.env.dev
Normal file
4
.env.dev
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
###> symfony/framework-bundle ###
|
||||||
|
APP_SECRET=7ac10a47af9d9582c01fe117c77e4c53
|
||||||
|
###< symfony/framework-bundle ###
|
||||||
3
.env.test
Normal file
3
.env.test
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# define your env variables for the test env here
|
||||||
|
KERNEL_CLASS='App\Kernel'
|
||||||
|
APP_SECRET='$ecretf0rt3st'
|
||||||
17
.php-cs-fixer.dist.php
Normal file
17
.php-cs-fixer.dist.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$finder = (new PhpCsFixer\Finder())
|
||||||
|
->in(__DIR__)
|
||||||
|
->exclude('var')
|
||||||
|
->notPath([
|
||||||
|
'config/bundles.php',
|
||||||
|
'config/reference.php',
|
||||||
|
])
|
||||||
|
;
|
||||||
|
|
||||||
|
return (new PhpCsFixer\Config())
|
||||||
|
->setRules([
|
||||||
|
'@Symfony' => true,
|
||||||
|
])
|
||||||
|
->setFinder($finder)
|
||||||
|
;
|
||||||
4
bin/phpunit
Executable file
4
bin/phpunit
Executable file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
||||||
94
docker/ollama-tunnel/setup.sh
Normal file
94
docker/ollama-tunnel/setup.sh
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Run this script on the LOCAL machine (where Ollama runs).
|
||||||
|
# It registers your SSH public key on the server and installs
|
||||||
|
# the autossh systemd service for a persistent tunnel.
|
||||||
|
#
|
||||||
|
# Usage: ./setup.sh <server-ip-or-hostname>
|
||||||
|
#
|
||||||
|
# Prerequisites (local machine):
|
||||||
|
# apt/brew: openssh-client autossh
|
||||||
|
# Ollama running on localhost:11434
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SERVER="${1:?Usage: $0 <server-ip-or-hostname>}"
|
||||||
|
TUNNEL_USER="ollama-tunnel"
|
||||||
|
REMOTE_PORT=11434
|
||||||
|
LOCAL_PORT=11434
|
||||||
|
KEY_FILE="${HOME}/.ssh/id_ed25519"
|
||||||
|
|
||||||
|
# Generate key if it doesn't exist
|
||||||
|
if [[ ! -f "${KEY_FILE}" ]]; then
|
||||||
|
echo "[+] Generating SSH key ${KEY_FILE} ..."
|
||||||
|
ssh-keygen -t ed25519 -f "${KEY_FILE}" -N "" -C "ollama-tunnel@$(hostname)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy public key to server
|
||||||
|
echo "[+] Copying public key to ${TUNNEL_USER}@${SERVER} ..."
|
||||||
|
echo " You will be prompted for sudo on the server (or use the superseller account)."
|
||||||
|
PUBKEY=$(cat "${KEY_FILE}.pub")
|
||||||
|
ssh superseller@"${SERVER}" "sudo bash -c 'echo \"${PUBKEY}\" >> /home/${TUNNEL_USER}/.ssh/authorized_keys && sort -u /home/${TUNNEL_USER}/.ssh/authorized_keys -o /home/${TUNNEL_USER}/.ssh/authorized_keys'"
|
||||||
|
|
||||||
|
echo "[+] Testing tunnel connection ..."
|
||||||
|
ssh -o StrictHostKeyChecking=accept-new -o ConnectTimeout=5 \
|
||||||
|
-N -i "${KEY_FILE}" \
|
||||||
|
-R "172.18.0.1:${REMOTE_PORT}:localhost:${LOCAL_PORT}" \
|
||||||
|
"${TUNNEL_USER}@${SERVER}" &
|
||||||
|
SSH_PID=$!
|
||||||
|
sleep 2
|
||||||
|
if kill -0 "${SSH_PID}" 2>/dev/null; then
|
||||||
|
echo "[+] Tunnel works! Stopping test connection."
|
||||||
|
kill "${SSH_PID}"
|
||||||
|
else
|
||||||
|
echo "[!] Tunnel test failed. Check sshd config and firewall on the server."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install systemd service
|
||||||
|
install_systemd_service() {
|
||||||
|
local service_file="${HOME}/.config/systemd/user/ollama-tunnel.service"
|
||||||
|
mkdir -p "$(dirname "${service_file}")"
|
||||||
|
cat > "${service_file}" << EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Ollama SSH reverse tunnel to SuperSeller3000 server
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/autossh -M 0 \\
|
||||||
|
-o "ServerAliveInterval=30" \\
|
||||||
|
-o "ServerAliveCountMax=3" \\
|
||||||
|
-o "ExitOnForwardFailure=yes" \\
|
||||||
|
-o "StrictHostKeyChecking=accept-new" \\
|
||||||
|
-N -i ${KEY_FILE} \\
|
||||||
|
-R 172.18.0.1:${REMOTE_PORT}:localhost:${LOCAL_PORT} \\
|
||||||
|
${TUNNEL_USER}@${SERVER}
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user enable ollama-tunnel.service
|
||||||
|
systemctl --user start ollama-tunnel.service
|
||||||
|
echo "[+] systemd service installed and started."
|
||||||
|
echo " Status: systemctl --user status ollama-tunnel"
|
||||||
|
}
|
||||||
|
|
||||||
|
if command -v autossh &>/dev/null && command -v systemctl &>/dev/null; then
|
||||||
|
echo "[+] Installing autossh systemd user service ..."
|
||||||
|
install_systemd_service
|
||||||
|
else
|
||||||
|
echo "[!] autossh or systemd not found. Manual tunnel command:"
|
||||||
|
echo ""
|
||||||
|
echo " autossh -M 0 -o ServerAliveInterval=30 -N \\"
|
||||||
|
echo " -i ${KEY_FILE} \\"
|
||||||
|
echo " -R 172.18.0.1:${REMOTE_PORT}:localhost:${LOCAL_PORT} \\"
|
||||||
|
echo " ${TUNNEL_USER}@${SERVER}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Done. The server will see Ollama at http://172.18.0.1:${REMOTE_PORT}"
|
||||||
3094
docs/superpowers/plans/2026-05-13-01-foundation.md
Normal file
3094
docs/superpowers/plans/2026-05-13-01-foundation.md
Normal file
File diff suppressed because it is too large
Load diff
2461
docs/superpowers/plans/2026-05-13-02-article-api.md
Normal file
2461
docs/superpowers/plans/2026-05-13-02-article-api.md
Normal file
File diff suppressed because it is too large
Load diff
1787
docs/superpowers/plans/2026-05-13-03-auth-logging.md
Normal file
1787
docs/superpowers/plans/2026-05-13-03-auth-logging.md
Normal file
File diff suppressed because it is too large
Load diff
1777
docs/superpowers/plans/2026-05-13-04-ai-pipelines.md
Normal file
1777
docs/superpowers/plans/2026-05-13-04-ai-pipelines.md
Normal file
File diff suppressed because it is too large
Load diff
1322
docs/superpowers/plans/2026-05-13-05-ebay-adapter.md
Normal file
1322
docs/superpowers/plans/2026-05-13-05-ebay-adapter.md
Normal file
File diff suppressed because it is too large
Load diff
1699
docs/superpowers/plans/2026-05-13-06-order-processing.md
Normal file
1699
docs/superpowers/plans/2026-05-13-06-order-processing.md
Normal file
File diff suppressed because it is too large
Load diff
44
phpunit.dist.xml
Normal file
44
phpunit.dist.xml
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||||
|
colors="true"
|
||||||
|
failOnDeprecation="true"
|
||||||
|
failOnNotice="true"
|
||||||
|
failOnWarning="true"
|
||||||
|
bootstrap="tests/bootstrap.php"
|
||||||
|
cacheDirectory=".phpunit.cache"
|
||||||
|
>
|
||||||
|
<php>
|
||||||
|
<ini name="display_errors" value="1" />
|
||||||
|
<ini name="error_reporting" value="-1" />
|
||||||
|
<server name="APP_ENV" value="test" force="true" />
|
||||||
|
<server name="SHELL_VERBOSITY" value="-1" />
|
||||||
|
</php>
|
||||||
|
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Project Test Suite">
|
||||||
|
<directory>tests</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<source ignoreSuppressionOfDeprecations="true"
|
||||||
|
ignoreIndirectDeprecations="true"
|
||||||
|
restrictNotices="true"
|
||||||
|
restrictWarnings="true"
|
||||||
|
>
|
||||||
|
<include>
|
||||||
|
<directory>src</directory>
|
||||||
|
</include>
|
||||||
|
|
||||||
|
<deprecationTrigger>
|
||||||
|
<method>Doctrine\Deprecations\Deprecation::trigger</method>
|
||||||
|
<method>Doctrine\Deprecations\Deprecation::delegateTriggerToBackend</method>
|
||||||
|
<function>trigger_deprecation</function>
|
||||||
|
</deprecationTrigger>
|
||||||
|
</source>
|
||||||
|
|
||||||
|
<extensions>
|
||||||
|
</extensions>
|
||||||
|
</phpunit>
|
||||||
|
|
@ -23,7 +23,7 @@ final class ArticleTypeServiceTest extends TestCase
|
||||||
$this->service = new ArticleTypeService($this->repo);
|
$this->service = new ArticleTypeService($this->repo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_create_saves_article_type(): void
|
public function testCreateSavesArticleType(): void
|
||||||
{
|
{
|
||||||
$this->repo->expects($this->once())->method('save');
|
$this->repo->expects($this->once())->method('save');
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ final class ArticleTypeServiceTest extends TestCase
|
||||||
$this->assertSame('Notebook', $type->getName());
|
$this->assertSame('Notebook', $type->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_rename_updates_name(): void
|
public function testRenameUpdatesName(): void
|
||||||
{
|
{
|
||||||
$type = new ArticleType('Notebook');
|
$type = new ArticleType('Notebook');
|
||||||
$this->repo->method('findById')->willReturn($type);
|
$this->repo->method('findById')->willReturn($type);
|
||||||
|
|
@ -43,7 +43,7 @@ final class ArticleTypeServiceTest extends TestCase
|
||||||
$this->assertSame('Laptop', $type->getName());
|
$this->assertSame('Laptop', $type->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_rename_throws_when_not_found(): void
|
public function testRenameThrowsWhenNotFound(): void
|
||||||
{
|
{
|
||||||
$this->repo->method('findById')->willReturn(null);
|
$this->repo->method('findById')->willReturn(null);
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ final class ArticleTypeServiceTest extends TestCase
|
||||||
$this->service->rename(\Symfony\Component\Uid\Uuid::v7(), 'X');
|
$this->service->rename(\Symfony\Component\Uid\Uuid::v7(), 'X');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_add_attribute_links_definition(): void
|
public function testAddAttributeLinksDefinition(): void
|
||||||
{
|
{
|
||||||
$type = new ArticleType('Notebook');
|
$type = new ArticleType('Notebook');
|
||||||
$def = new AttributeDefinition('RAM', AttributeType::String);
|
$def = new AttributeDefinition('RAM', AttributeType::String);
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ final class ArticleValidatorTest extends TestCase
|
||||||
$this->type->addAttributeDefinition($this->cpuDef);
|
$this->type->addAttributeDefinition($this->cpuDef);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_valid_when_all_attributes_set(): void
|
public function testValidWhenAllAttributesSet(): void
|
||||||
{
|
{
|
||||||
$article = new Article($this->type, 'NB-001', 'INV-001', 1, ArticleCondition::Good);
|
$article = new Article($this->type, 'NB-001', 'INV-001', 1, ArticleCondition::Good);
|
||||||
$article->setAttributeValue(new AttributeValue($article, $this->ramDef, '16 GB'));
|
$article->setAttributeValue(new AttributeValue($article, $this->ramDef, '16 GB'));
|
||||||
|
|
@ -42,7 +42,7 @@ final class ArticleValidatorTest extends TestCase
|
||||||
$this->assertTrue($this->validator->isValid($article));
|
$this->assertTrue($this->validator->isValid($article));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_returns_missing_attribute_names(): void
|
public function testReturnsMissingAttributeNames(): void
|
||||||
{
|
{
|
||||||
$article = new Article($this->type, 'NB-001', 'INV-001', 1, ArticleCondition::Good);
|
$article = new Article($this->type, 'NB-001', 'INV-001', 1, ArticleCondition::Good);
|
||||||
$article->setAttributeValue(new AttributeValue($article, $this->ramDef, '16 GB'));
|
$article->setAttributeValue(new AttributeValue($article, $this->ramDef, '16 GB'));
|
||||||
|
|
@ -55,7 +55,7 @@ final class ArticleValidatorTest extends TestCase
|
||||||
$this->assertFalse($this->validator->isValid($article));
|
$this->assertFalse($this->validator->isValid($article));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_all_missing_when_no_values_set(): void
|
public function testAllMissingWhenNoValuesSet(): void
|
||||||
{
|
{
|
||||||
$article = new Article($this->type, 'NB-001', 'INV-001', 1, ArticleCondition::Good);
|
$article = new Article($this->type, 'NB-001', 'INV-001', 1, ArticleCondition::Good);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ final class LocalStorageManagerTest extends TestCase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_store_picks_active_path_with_quota(): void
|
public function testStorePicksActivePathWithQuota(): void
|
||||||
{
|
{
|
||||||
$path = new StoragePath('Main', sys_get_temp_dir().'/storage-test-'.uniqid(), 1_000_000, 10);
|
$path = new StoragePath('Main', sys_get_temp_dir().'/storage-test-'.uniqid(), 1_000_000, 10);
|
||||||
mkdir($path->getBasePath(), recursive: true);
|
mkdir($path->getBasePath(), recursive: true);
|
||||||
|
|
@ -51,7 +51,7 @@ final class LocalStorageManagerTest extends TestCase
|
||||||
rmdir($path->getBasePath());
|
rmdir($path->getBasePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_throws_when_no_active_path(): void
|
public function testThrowsWhenNoActivePath(): void
|
||||||
{
|
{
|
||||||
$this->repo->method('findActiveSortedByPriority')->willReturn([]);
|
$this->repo->method('findActiveSortedByPriority')->willReturn([]);
|
||||||
|
|
||||||
|
|
@ -61,7 +61,7 @@ final class LocalStorageManagerTest extends TestCase
|
||||||
$this->manager->store($this->tmpFile, 'photo.jpg');
|
$this->manager->store($this->tmpFile, 'photo.jpg');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_skips_full_path_and_uses_next(): void
|
public function testSkipsFullPathAndUsesNext(): void
|
||||||
{
|
{
|
||||||
$fullPath = new StoragePath('Full', sys_get_temp_dir().'/full-'.uniqid(), 50, 20);
|
$fullPath = new StoragePath('Full', sys_get_temp_dir().'/full-'.uniqid(), 50, 20);
|
||||||
$okPath = new StoragePath('OK', sys_get_temp_dir().'/ok-'.uniqid(), 1_000_000, 10);
|
$okPath = new StoragePath('OK', sys_get_temp_dir().'/ok-'.uniqid(), 1_000_000, 10);
|
||||||
|
|
@ -80,7 +80,7 @@ final class LocalStorageManagerTest extends TestCase
|
||||||
rmdir($okPath->getBasePath());
|
rmdir($okPath->getBasePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_get_full_path(): void
|
public function testGetFullPath(): void
|
||||||
{
|
{
|
||||||
$path = new StoragePath('Main', '/srv/storage', 1_000_000, 10);
|
$path = new StoragePath('Main', '/srv/storage', 1_000_000, 10);
|
||||||
$this->assertSame('/srv/storage/photo.jpg', $this->manager->getFullPath($path, 'photo.jpg'));
|
$this->assertSame('/srv/storage/photo.jpg', $this->manager->getFullPath($path, 'photo.jpg'));
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
final class ArticleStatusTest extends TestCase
|
final class ArticleStatusTest extends TestCase
|
||||||
{
|
{
|
||||||
public function test_valid_transitions(): void
|
public function testValidTransitions(): void
|
||||||
{
|
{
|
||||||
$this->assertTrue(ArticleStatus::Ingesting->canTransitionTo(ArticleStatus::Draft));
|
$this->assertTrue(ArticleStatus::Ingesting->canTransitionTo(ArticleStatus::Draft));
|
||||||
$this->assertTrue(ArticleStatus::Draft->canTransitionTo(ArticleStatus::Active));
|
$this->assertTrue(ArticleStatus::Draft->canTransitionTo(ArticleStatus::Active));
|
||||||
|
|
@ -19,7 +19,7 @@ final class ArticleStatusTest extends TestCase
|
||||||
$this->assertTrue(ArticleStatus::Listed->canTransitionTo(ArticleStatus::Sold));
|
$this->assertTrue(ArticleStatus::Listed->canTransitionTo(ArticleStatus::Sold));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_invalid_transitions(): void
|
public function testInvalidTransitions(): void
|
||||||
{
|
{
|
||||||
$this->assertFalse(ArticleStatus::Sold->canTransitionTo(ArticleStatus::Draft));
|
$this->assertFalse(ArticleStatus::Sold->canTransitionTo(ArticleStatus::Draft));
|
||||||
$this->assertFalse(ArticleStatus::Ingesting->canTransitionTo(ArticleStatus::Sold));
|
$this->assertFalse(ArticleStatus::Ingesting->canTransitionTo(ArticleStatus::Sold));
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ final class ArticleTest extends TestCase
|
||||||
$this->type = new ArticleType('Notebook');
|
$this->type = new ArticleType('Notebook');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_new_article_has_ingesting_status(): void
|
public function testNewArticleHasIngestingStatus(): void
|
||||||
{
|
{
|
||||||
$article = new Article($this->type, 'NB-001', 'INV-001', 1, ArticleCondition::Good);
|
$article = new Article($this->type, 'NB-001', 'INV-001', 1, ArticleCondition::Good);
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ final class ArticleTest extends TestCase
|
||||||
$this->assertSame(1, $article->getStock());
|
$this->assertSame(1, $article->getStock());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_valid_status_transition(): void
|
public function testValidStatusTransition(): void
|
||||||
{
|
{
|
||||||
$article = new Article($this->type, 'NB-001', 'INV-001', 1, ArticleCondition::Good);
|
$article = new Article($this->type, 'NB-001', 'INV-001', 1, ArticleCondition::Good);
|
||||||
$article->transitionTo(ArticleStatus::Draft);
|
$article->transitionTo(ArticleStatus::Draft);
|
||||||
|
|
@ -35,7 +35,7 @@ final class ArticleTest extends TestCase
|
||||||
$this->assertSame(ArticleStatus::Draft, $article->getStatus());
|
$this->assertSame(ArticleStatus::Draft, $article->getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_invalid_status_transition_throws(): void
|
public function testInvalidStatusTransitionThrows(): void
|
||||||
{
|
{
|
||||||
$article = new Article($this->type, 'NB-001', 'INV-001', 1, ArticleCondition::Good);
|
$article = new Article($this->type, 'NB-001', 'INV-001', 1, ArticleCondition::Good);
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ final class ArticleTest extends TestCase
|
||||||
$article->transitionTo(ArticleStatus::Sold);
|
$article->transitionTo(ArticleStatus::Sold);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_decrement_stock(): void
|
public function testDecrementStock(): void
|
||||||
{
|
{
|
||||||
$article = new Article($this->type, 'NB-001', 'INV-001', 3, ArticleCondition::Good);
|
$article = new Article($this->type, 'NB-001', 'INV-001', 3, ArticleCondition::Good);
|
||||||
$article->decrementStock();
|
$article->decrementStock();
|
||||||
|
|
@ -52,7 +52,7 @@ final class ArticleTest extends TestCase
|
||||||
$this->assertFalse($article->isOutOfStock());
|
$this->assertFalse($article->isOutOfStock());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_decrement_to_zero_marks_out_of_stock(): void
|
public function testDecrementToZeroMarksOutOfStock(): void
|
||||||
{
|
{
|
||||||
$article = new Article($this->type, 'NB-001', 'INV-001', 1, ArticleCondition::Good);
|
$article = new Article($this->type, 'NB-001', 'INV-001', 1, ArticleCondition::Good);
|
||||||
$article->decrementStock();
|
$article->decrementStock();
|
||||||
|
|
@ -60,7 +60,7 @@ final class ArticleTest extends TestCase
|
||||||
$this->assertTrue($article->isOutOfStock());
|
$this->assertTrue($article->isOutOfStock());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_decrement_below_zero_throws(): void
|
public function testDecrementBelowZeroThrows(): void
|
||||||
{
|
{
|
||||||
$article = new Article($this->type, 'NB-001', 'INV-001', 0, ArticleCondition::Good);
|
$article = new Article($this->type, 'NB-001', 'INV-001', 0, ArticleCondition::Good);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
final class CustomerTest extends TestCase
|
final class CustomerTest extends TestCase
|
||||||
{
|
{
|
||||||
public function test_new_customer_has_empty_platform_ids(): void
|
public function testNewCustomerHasEmptyPlatformIds(): void
|
||||||
{
|
{
|
||||||
$customer = new Customer('Max Mustermann', 'max@example.com', []);
|
$customer = new Customer('Max Mustermann', 'max@example.com', []);
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ final class CustomerTest extends TestCase
|
||||||
$this->assertNull($customer->getPlatformId('ebay'));
|
$this->assertNull($customer->getPlatformId('ebay'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_add_platform_id(): void
|
public function testAddPlatformId(): void
|
||||||
{
|
{
|
||||||
$customer = new Customer('Max Mustermann', 'max@example.com', []);
|
$customer = new Customer('Max Mustermann', 'max@example.com', []);
|
||||||
$customer->addPlatformId('ebay', 'ebay-user-123');
|
$customer->addPlatformId('ebay', 'ebay-user-123');
|
||||||
|
|
@ -25,7 +25,7 @@ final class CustomerTest extends TestCase
|
||||||
$this->assertSame('ebay-user-123', $customer->getPlatformId('ebay'));
|
$this->assertSame('ebay-user-123', $customer->getPlatformId('ebay'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_matching_key_is_lowercase_normalized(): void
|
public function testMatchingKeyIsLowercaseNormalized(): void
|
||||||
{
|
{
|
||||||
$customer = new Customer('Max Mustermann', 'max@example.com', [
|
$customer = new Customer('Max Mustermann', 'max@example.com', [
|
||||||
'street' => 'Musterstraße 1',
|
'street' => 'Musterstraße 1',
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Symfony\Component\Dotenv\Dotenv;
|
use Symfony\Component\Dotenv\Dotenv;
|
||||||
|
|
||||||
require dirname(__DIR__).'/vendor/autoload.php';
|
require dirname(__DIR__).'/vendor/autoload.php';
|
||||||
|
|
||||||
if (method_exists(Dotenv::class, 'bootEnv')) {
|
(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
|
||||||
(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($_SERVER['APP_DEBUG']) {
|
if ($_SERVER['APP_DEBUG']) {
|
||||||
umask(0000);
|
umask(0000);
|
||||||
|
|
|
||||||
0
translations/.gitignore
vendored
Normal file
0
translations/.gitignore
vendored
Normal file
Loading…
Reference in a new issue