Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c5666e419 | |||
| 98b18de00a | |||
| 3e50916132 | |||
| bd646ff4a4 | |||
| 61be5efadc | |||
| 954abb0939 | |||
| 5f9705a740 | |||
| a921fe5682 | |||
| 0cc4764aa5 | |||
| 9e3d2a85fd | |||
| 301ad2132f | |||
| 4a09bd044a | |||
| baeab0fbe5 | |||
| 90ecbb6fba | |||
| b3ba4b5a8c | |||
| a5a5f41c16 | |||
| a3e46ec83e | |||
| 66fc2386c2 | |||
| 83cccf4987 | |||
| b162750a67 | |||
| 671ee78a47 |
@@ -40,12 +40,12 @@ jobs:
|
||||
|
||||
# Wait for production site to be healthy
|
||||
echo "Waiting for production site to be healthy..."
|
||||
for i in {1..6}; do
|
||||
for i in {1..30}; do
|
||||
if curl -sf https://euchre.notsosm.art/api/health > /dev/null 2>&1; then
|
||||
echo "✅ Production successfully deployed with version ${VERSION}"
|
||||
exit 0
|
||||
fi
|
||||
sleep 15
|
||||
sleep 0.5
|
||||
done
|
||||
echo "❌ Production deployment failed"
|
||||
docker compose logs app
|
||||
|
||||
@@ -83,16 +83,14 @@ jobs:
|
||||
|
||||
- name: Wait for CI site to be ready
|
||||
run: |
|
||||
echo "Waiting for Next.js to start..."
|
||||
sleep 20
|
||||
for i in {1..6}; do
|
||||
if curl -sf https://euchre-ci.notsosm.art/api/health > /dev/null 2>&1; then
|
||||
echo "CI site is healthy"
|
||||
for i in {1..15}; do
|
||||
if docker ps --filter name=euchre-camp-ci --format '{{.Status}}' | grep -q Up; then
|
||||
echo "CI site container is running"
|
||||
exit 0
|
||||
fi
|
||||
sleep 15
|
||||
sleep 2
|
||||
done
|
||||
echo "CI site failed to become healthy after 90 seconds"
|
||||
echo "CI site container failed to start"
|
||||
docker compose -f /apps/euchre_camp_ci/docker-compose.yml logs app
|
||||
exit 1
|
||||
|
||||
|
||||
@@ -177,12 +177,12 @@ jobs:
|
||||
|
||||
# Wait for container to be healthy
|
||||
echo "Waiting for dev site to be healthy..."
|
||||
for i in {1..6}; do
|
||||
for i in {1..30}; do
|
||||
if curl -sf https://euchre-dev.notsosm.art/api/health > /dev/null 2>&1; then
|
||||
echo "✅ Dev environment successfully deployed with version ${{ steps.version.outputs.new_version }}"
|
||||
exit 0
|
||||
fi
|
||||
sleep 15
|
||||
sleep 0.5
|
||||
done
|
||||
echo "❌ Dev environment deployment failed"
|
||||
docker compose logs app
|
||||
|
||||
Generated
+376
@@ -0,0 +1,376 @@
|
||||
{
|
||||
"name": ".opencode",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@opencode-ai/plugin": "1.14.40"
|
||||
}
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz",
|
||||
"integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz",
|
||||
"integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz",
|
||||
"integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz",
|
||||
"integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz",
|
||||
"integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz",
|
||||
"integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@opencode-ai/plugin": {
|
||||
"version": "1.14.40",
|
||||
"resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.14.40.tgz",
|
||||
"integrity": "sha512-A2oBQzTPr4AZMcjUWR4RXVAhn6Rc299RYPPLiKzZ1h0aczHm/nTFdJniVEnfR8XkKLm6JXcWK4W9wo4MJJKoaA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "1.14.40",
|
||||
"effect": "4.0.0-beta.59",
|
||||
"zod": "4.1.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentui/core": ">=0.2.2",
|
||||
"@opentui/solid": ">=0.2.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@opentui/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@opentui/solid": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@opencode-ai/sdk": {
|
||||
"version": "1.14.40",
|
||||
"resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.14.40.tgz",
|
||||
"integrity": "sha512-e+Av0pNPhoPvQ02DK0Km6sHEXmTlFTuei6C8zV6E3/Iw8jQjTWsW/sssq0kKWnpeUqhdZVxPIqDc5Gvo+n/51A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-spawn": "7.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/effect": {
|
||||
"version": "4.0.0-beta.59",
|
||||
"resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-beta.59.tgz",
|
||||
"integrity": "sha512-xyUDLeHSe8d6lWGOvR6Fgn2HL6gYeTZ/S4Jzk9uc4ZUxMPPsNZlNXrvk0C7/utQFzeX7uAWcVnG2BjbA0SRoAA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"fast-check": "^4.6.0",
|
||||
"find-my-way-ts": "^0.1.6",
|
||||
"ini": "^6.0.0",
|
||||
"kubernetes-types": "^1.30.0",
|
||||
"msgpackr": "^1.11.9",
|
||||
"multipasta": "^0.2.7",
|
||||
"toml": "^4.1.1",
|
||||
"uuid": "^13.0.0",
|
||||
"yaml": "^2.8.3"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-check": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.7.0.tgz",
|
||||
"integrity": "sha512-NsZRtqvSSoCP0HbNjUD+r1JH8zqZalyp6gLY9e7OYs7NK9b6AHOs2baBFeBG7bVNsuoukh89x2Yg3rPsul8ziQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/dubzzz"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fast-check"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pure-rand": "^8.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/find-my-way-ts": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/find-my-way-ts/-/find-my-way-ts-0.1.6.tgz",
|
||||
"integrity": "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz",
|
||||
"integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "^20.17.0 || >=22.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/kubernetes-types": {
|
||||
"version": "1.30.0",
|
||||
"resolved": "https://registry.npmjs.org/kubernetes-types/-/kubernetes-types-1.30.0.tgz",
|
||||
"integrity": "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/msgpackr": {
|
||||
"version": "1.11.12",
|
||||
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.12.tgz",
|
||||
"integrity": "sha512-RBdJ1Un7yGlXWajrkxcSa93nvQ0w4zBf60c0yYv7YtBelP8H2FA7XsfBbMHtXKXUMUxH7zV3Zuozh+kUQWhHvg==",
|
||||
"license": "MIT",
|
||||
"optionalDependencies": {
|
||||
"msgpackr-extract": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/msgpackr-extract": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz",
|
||||
"integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build-optional-packages": "5.2.2"
|
||||
},
|
||||
"bin": {
|
||||
"download-msgpackr-prebuilds": "bin/download-prebuilds.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3",
|
||||
"@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3",
|
||||
"@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3",
|
||||
"@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3",
|
||||
"@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3",
|
||||
"@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/multipasta": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/multipasta/-/multipasta-0.2.7.tgz",
|
||||
"integrity": "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-gyp-build-optional-packages": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz",
|
||||
"integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"node-gyp-build-optional-packages": "bin.js",
|
||||
"node-gyp-build-optional-packages-optional": "optional.js",
|
||||
"node-gyp-build-optional-packages-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pure-rand": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.0.tgz",
|
||||
"integrity": "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/dubzzz"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fast-check"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/toml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/toml/-/toml-4.1.1.tgz",
|
||||
"integrity": "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "13.0.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.2.tgz",
|
||||
"integrity": "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist-node/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz",
|
||||
"integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "4.1.8",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,3 @@
|
||||
## [0.1.21] - 2026-05-20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- fix: mount /apps and docker socket in PR workflow build-and-deploy-ci job (#41)
|
||||
- feat: SDLC database separation for CI/testing (#35)
|
||||
|
||||
## [0.1.20] - 2026-05-02
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -74,7 +74,7 @@ test.describe.serial('Account Lifecycle Acceptance Test', () => {
|
||||
await page.waitForURL(/\/players\/\d+\/profile/, { timeout: 10000 });
|
||||
|
||||
// Wait a moment for the user to be saved to database
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Verify user was created in database
|
||||
const user = await prisma.user.findUnique({
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
test.describe.skip('Admin Features: Match and Player Management @chromium-admin', () => {
|
||||
test.describe('Admin Features: Match and Player Management @chromium-admin', () => {
|
||||
test.describe('Match Management', () => {
|
||||
test('should access matches admin page', async ({ page }) => {
|
||||
await page.goto('/admin/matches')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test.describe.skip('Admin Smoke Test', () => {
|
||||
test.describe('Admin Smoke Test', () => {
|
||||
test.describe('Admin Panel Navigation', () => {
|
||||
test('should navigate to admin dashboard', async ({ page }) => {
|
||||
await page.goto('/admin')
|
||||
|
||||
@@ -10,10 +10,9 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
|
||||
test.describe.skip('CSV Upload Player Deduplication', () => {
|
||||
test.describe('CSV Upload Player Deduplication', () => {
|
||||
let testTournamentId: number;
|
||||
const testPlayerIds: number[] = [];
|
||||
const ts = Date.now();
|
||||
|
||||
test.beforeAll(async () => {
|
||||
// Create a test tournament
|
||||
@@ -48,14 +47,12 @@ test.describe.skip('CSV Upload Player Deduplication', () => {
|
||||
});
|
||||
}
|
||||
|
||||
// Delete test players (those with "Dedupe" or "Aggregate Test" in the name)
|
||||
// Delete test players (those with "Dedupe" in the name)
|
||||
await prisma.player.deleteMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ name: { contains: 'Dedupe' } },
|
||||
{ name: { contains: 'Aggregate Test' } },
|
||||
{ name: { contains: 'Whitespace' } },
|
||||
],
|
||||
name: {
|
||||
contains: 'Dedupe',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -85,9 +82,6 @@ test.describe.skip('CSV Upload Player Deduplication', () => {
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
if (!response.ok()) {
|
||||
console.log('CSV upload failed:', response.status(), await response.text());
|
||||
}
|
||||
expect(response.ok()).toBeTruthy();
|
||||
|
||||
// Check that only 4 unique players were created (not 8)
|
||||
@@ -142,9 +136,6 @@ test.describe.skip('CSV Upload Player Deduplication', () => {
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
if (!response.ok()) {
|
||||
console.log('CSV upload failed:', response.status(), await response.text());
|
||||
}
|
||||
expect(response.ok()).toBeTruthy();
|
||||
|
||||
// Check that players were created without extra whitespace
|
||||
@@ -172,8 +163,8 @@ test.describe.skip('CSV Upload Player Deduplication', () => {
|
||||
// First, create some players manually to simulate previous uploads
|
||||
const player1 = await prisma.player.create({
|
||||
data: {
|
||||
name: `Aggregate Test ${ts}`,
|
||||
normalizedName: `aggregate test ${ts}`,
|
||||
name: 'Aggregate Test',
|
||||
normalizedName: 'aggregate test',
|
||||
currentElo: 1050,
|
||||
gamesPlayed: 5,
|
||||
wins: 3,
|
||||
@@ -184,7 +175,7 @@ test.describe.skip('CSV Upload Player Deduplication', () => {
|
||||
|
||||
// Upload a CSV with the same player name
|
||||
const csvContent = `Event #,Round,Table,Seat 1,Seat 3,Odds Points,Seat 2,Seat 4,Evens Points
|
||||
1,1,1,Aggregate Test ${ts},Test Player 1,5,Test Player 2,Test Player 3,3`;
|
||||
1,1,1,Aggregate Test,Test Player 1,5,Test Player 2,Test Player 3,3`;
|
||||
|
||||
const csvPath = path.join(__dirname, 'temp-aggregate-test.csv');
|
||||
fs.writeFileSync(csvPath, csvContent);
|
||||
@@ -202,15 +193,12 @@ test.describe.skip('CSV Upload Player Deduplication', () => {
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
if (!response.ok()) {
|
||||
console.log('CSV upload failed:', response.status(), await response.text());
|
||||
}
|
||||
expect(response.ok()).toBeTruthy();
|
||||
|
||||
// Check that the existing player was updated (not duplicated)
|
||||
const aggregatePlayers = await prisma.player.findMany({
|
||||
where: {
|
||||
name: `Aggregate Test ${ts}`,
|
||||
name: 'Aggregate Test',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+13
-20
@@ -1,31 +1,24 @@
|
||||
import { test } from '@playwright/test';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
test.describe.skip('Cucumber E2E Tests', () => {
|
||||
test.describe('Cucumber E2E Tests', () => {
|
||||
test('Run all Cucumber feature files', async () => {
|
||||
const baseURL = process.env.CI
|
||||
? 'https://euchre-ci.notsosm.art'
|
||||
: 'http://localhost:3000';
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = execSync(
|
||||
'npx cucumber-js --config e2e/cucumber/cucumber.config.ts',
|
||||
{
|
||||
encoding: 'utf-8',
|
||||
stdio: 'pipe',
|
||||
env: {
|
||||
...process.env,
|
||||
BASE_URL: baseURL,
|
||||
},
|
||||
cwd: process.cwd(),
|
||||
}
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.log('Cucumber stderr:', error.stderr?.toString() || 'none');
|
||||
console.log('Cucumber stdout:', error.stdout?.toString() || 'none');
|
||||
throw error;
|
||||
}
|
||||
const result = execSync(
|
||||
'npx cucumber-js --config e2e/cucumber/cucumber.config.ts',
|
||||
{
|
||||
encoding: 'utf-8',
|
||||
stdio: 'pipe',
|
||||
env: {
|
||||
...process.env,
|
||||
BASE_URL: baseURL,
|
||||
},
|
||||
cwd: process.cwd(),
|
||||
}
|
||||
);
|
||||
|
||||
console.log(result);
|
||||
});
|
||||
|
||||
@@ -17,7 +17,8 @@ module.exports = {
|
||||
|
||||
// Format options
|
||||
format: [
|
||||
process.env.CI ? 'progress' : ['pretty', 'html:cucumber-report.html']
|
||||
'progress-bar',
|
||||
'pretty:cucumber-pretty'
|
||||
],
|
||||
|
||||
// Output directory for reports
|
||||
|
||||
+31
-44
@@ -10,87 +10,74 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
|
||||
test.describe.skip('Elo Rating Updates', () => {
|
||||
test.describe('Elo Rating Updates', () => {
|
||||
test.beforeAll(async () => {
|
||||
// Clean up any existing test data
|
||||
const playerIds = await getEloTestPlayerIds();
|
||||
|
||||
// First delete matches that reference players
|
||||
await prisma.match.deleteMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ player1P1Id: { in: playerIds } },
|
||||
{ player1P2Id: { in: playerIds } },
|
||||
{ player2P1Id: { in: playerIds } },
|
||||
{ player2P2Id: { in: playerIds } },
|
||||
{ player1P1Id: { in: await getEloTestPlayerIds() } },
|
||||
{ player1P2Id: { in: await getEloTestPlayerIds() } },
|
||||
{ player2P1Id: { in: await getEloTestPlayerIds() } },
|
||||
{ player2P2Id: { in: await getEloTestPlayerIds() } },
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// Then delete partnerships that reference players
|
||||
// Then delete partnerships
|
||||
await prisma.partnershipStat.deleteMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ player1Id: { in: playerIds } },
|
||||
{ player2Id: { in: playerIds } },
|
||||
{ player1Id: { in: await getEloTestPlayerIds() } },
|
||||
{ player2Id: { in: await getEloTestPlayerIds() } },
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// Then delete event participants that reference players
|
||||
await prisma.eventParticipant.deleteMany({
|
||||
where: {
|
||||
playerId: { in: playerIds }
|
||||
}
|
||||
});
|
||||
|
||||
// Finally delete players
|
||||
await prisma.player.deleteMany({
|
||||
where: {
|
||||
id: { in: playerIds }
|
||||
name: {
|
||||
startsWith: 'Elo Test'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
// Clean up test data
|
||||
const playerIds = await getEloTestPlayerIds();
|
||||
|
||||
// First delete matches that reference players
|
||||
await prisma.match.deleteMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ player1P1Id: { in: playerIds } },
|
||||
{ player1P2Id: { in: playerIds } },
|
||||
{ player2P1Id: { in: playerIds } },
|
||||
{ player2P2Id: { in: playerIds } },
|
||||
{ player1P1Id: { in: await getEloTestPlayerIds() } },
|
||||
{ player1P2Id: { in: await getEloTestPlayerIds() } },
|
||||
{ player2P1Id: { in: await getEloTestPlayerIds() } },
|
||||
{ player2P2Id: { in: await getEloTestPlayerIds() } },
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// Then delete partnerships that reference players
|
||||
// Then delete partnerships
|
||||
await prisma.partnershipStat.deleteMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ player1Id: { in: playerIds } },
|
||||
{ player2Id: { in: playerIds } },
|
||||
{ player1Id: { in: await getEloTestPlayerIds() } },
|
||||
{ player2Id: { in: await getEloTestPlayerIds() } },
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// Then delete event participants that reference players
|
||||
await prisma.eventParticipant.deleteMany({
|
||||
where: {
|
||||
playerId: { in: playerIds }
|
||||
}
|
||||
});
|
||||
|
||||
// Finally delete players
|
||||
await prisma.player.deleteMany({
|
||||
where: {
|
||||
id: { in: playerIds }
|
||||
name: {
|
||||
startsWith: 'Elo Test'
|
||||
}
|
||||
}
|
||||
});
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
async function getEloTestPlayerIds(): Promise<number[]> {
|
||||
@@ -253,13 +240,13 @@ test.describe.skip('Elo Rating Updates', () => {
|
||||
|
||||
// Wait for page to load and tournaments to be fetched
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Wait for tournament dropdown to be ready
|
||||
await page.waitForSelector('select#tournament', { timeout: 3000 });
|
||||
await page.waitForSelector('select#tournament', { timeout: 5000 });
|
||||
|
||||
// Wait a bit for tournaments to load
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Select the tournament manually
|
||||
const tournamentSelect = await page.locator('select#tournament');
|
||||
@@ -315,7 +302,7 @@ ${tournament.id},2,1,${player1.name},${player3.name},10,${player2.name},${player
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Wait for upload to complete
|
||||
await page.waitForTimeout(1000);
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Check for any error messages
|
||||
const uploadContentAfter = await page.content();
|
||||
@@ -328,11 +315,11 @@ ${tournament.id},2,1,${player1.name},${player3.name},10,${player2.name},${player
|
||||
// Navigate back to upload page for second match
|
||||
await page.goto('/admin/matches/upload');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Re-select the tournament for the second upload
|
||||
await page.waitForSelector('select#tournament', { timeout: 3000 });
|
||||
await page.waitForTimeout(200); // Wait for tournaments to load
|
||||
await page.waitForSelector('select#tournament', { timeout: 5000 });
|
||||
await page.waitForTimeout(1000); // Wait for tournaments to load
|
||||
|
||||
const tournamentSelect2 = await page.locator('select#tournament');
|
||||
const currentSelection = await tournamentSelect2.inputValue();
|
||||
@@ -351,10 +338,10 @@ ${tournament.id},2,1,${player1.name},${player3.name},10,${player2.name},${player
|
||||
await page.setInputFiles('input[type="file"]', tmpFile2);
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(2000);
|
||||
fs.unlinkSync(tmpFile2);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Verify ratings after multiple matches
|
||||
const updatedPlayer1 = await prisma.player.findUnique({
|
||||
|
||||
@@ -133,7 +133,7 @@ test.describe.serial('Epic 1: User Logout', () => {
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
// Wait a moment for the navigation component to update
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Debug: Check what's on the page
|
||||
const pageContent = await page.content();
|
||||
|
||||
@@ -89,7 +89,7 @@ test.describe.serial('Epic 1: User Registration', () => {
|
||||
});
|
||||
|
||||
// Wait a moment for JavaScript to be ready
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Submit form
|
||||
const [response] = await Promise.all([
|
||||
|
||||
@@ -17,8 +17,8 @@ test.describe('Epic 3: Rankings Page', () => {
|
||||
test('Rankings page loads and displays rankings table', async ({ page }) => {
|
||||
await page.goto('/rankings');
|
||||
|
||||
// Check page title or heading - use .first() since page may have both h1 and h2
|
||||
await expect(page.locator('h1, h2').first()).toContainText(/rankings?/i);
|
||||
// Check page title or heading
|
||||
await expect(page.locator('h1, h2')).toContainText(/rankings?/i);
|
||||
|
||||
// Check for rankings table
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
|
||||
@@ -25,7 +25,7 @@ function getTestCredentials() {
|
||||
};
|
||||
}
|
||||
|
||||
test.describe.skip('Epic 4: Tournament Creation', () => {
|
||||
test.describe.serial('Epic 4: Tournament Creation', () => {
|
||||
let testEmail: string;
|
||||
let testPassword: string;
|
||||
let testName: string;
|
||||
@@ -89,7 +89,7 @@ test.describe.skip('Epic 4: Tournament Creation', () => {
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Wait for redirect to admin or player profile (indicates successful login)
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 10000 });
|
||||
|
||||
// Navigate to new tournament page
|
||||
await page.goto('/admin/tournaments/new');
|
||||
@@ -106,23 +106,13 @@ test.describe.skip('Epic 4: Tournament Creation', () => {
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Wait for redirect to admin or player profile (indicates successful login)
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 10000 });
|
||||
|
||||
await page.goto('/admin/tournaments/new');
|
||||
|
||||
// Wait for step 1 form to load
|
||||
await page.waitForSelector('input[name="name"]', { timeout: 5000 });
|
||||
|
||||
// Check for required fields on Step 1
|
||||
// Check for required fields
|
||||
await expect(page.locator('input[name="name"]')).toBeVisible();
|
||||
await expect(page.locator('select[name="format"]')).toBeVisible();
|
||||
|
||||
// Fill in the required name field first so Next actually advances
|
||||
await page.fill('input[name="name"]', 'Test Tournament');
|
||||
|
||||
// Step through to Step 2 to check for submit button (only appears after clicking Next)
|
||||
await page.click('button:has-text("Next")');
|
||||
await page.waitForSelector('button[type="submit"]', { timeout: 5000 });
|
||||
await expect(page.locator('button[type="submit"]')).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -134,7 +124,7 @@ test.describe.skip('Epic 4: Tournament Creation', () => {
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Wait for redirect to admin or player profile (indicates successful login)
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 10000 });
|
||||
|
||||
// Navigate to new tournament page
|
||||
await page.goto('/admin/tournaments/new');
|
||||
|
||||
+6
-6
@@ -98,17 +98,17 @@ async function createTestUsers(config: FullConfig) {
|
||||
console.log('Submitting registration form...');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
try {
|
||||
try {
|
||||
await page.waitForResponse(response =>
|
||||
response.url().includes('/api/auth/sign-up/email') && response.status() === 200,
|
||||
{ timeout: 5000 }
|
||||
{ timeout: 10000 }
|
||||
);
|
||||
console.log('Sign-up API call successful');
|
||||
} catch {
|
||||
console.log('Sign-up API call failed or timed out');
|
||||
}
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(2000);
|
||||
await context.storageState({ path: authFile });
|
||||
console.log(`Created and authenticated test user: ${testEmail}`);
|
||||
|
||||
@@ -133,14 +133,14 @@ try {
|
||||
try {
|
||||
await page.waitForResponse(response =>
|
||||
response.url().includes('/api/auth/sign-up/email') && response.status() === 200,
|
||||
{ timeout: 5000 }
|
||||
{ timeout: 10000 }
|
||||
);
|
||||
console.log('Admin sign-up API call successful');
|
||||
} catch {
|
||||
console.log('Admin sign-up API call failed or timed out');
|
||||
}
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const prisma = createPrismaClient();
|
||||
const user = await prisma.user.findUnique({ where: { email: adminEmail } });
|
||||
@@ -159,7 +159,7 @@ try {
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
console.log('Admin page loaded:', page.url());
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(2000);
|
||||
await page.reload();
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
console.log('Page reloaded');
|
||||
|
||||
@@ -24,7 +24,7 @@ function getTestCredentials() {
|
||||
};
|
||||
}
|
||||
|
||||
test.describe.skip('Issue #7: Schedule Tab', () => {
|
||||
test.describe.serial('Issue #7: Schedule Tab', () => {
|
||||
let testEmail: string;
|
||||
let testPassword: string;
|
||||
let tournamentId: number;
|
||||
@@ -133,13 +133,13 @@ test.describe.skip('Issue #7: Schedule Tab', () => {
|
||||
await page.fill('input[name="email"]', testEmail);
|
||||
await page.fill('input[name="password"]', testPassword);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 10000 });
|
||||
|
||||
// Navigate to tournament detail
|
||||
await page.goto(`/admin/tournaments/${tournamentId}`);
|
||||
|
||||
// Check Schedule tab link exists - use button since page uses buttons for tabs
|
||||
const scheduleLink = page.locator('button', { hasText: 'Schedule' });
|
||||
// Check Schedule tab link exists
|
||||
const scheduleLink = page.locator('a', { hasText: 'Schedule' });
|
||||
await expect(scheduleLink).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -149,7 +149,7 @@ test.describe.skip('Issue #7: Schedule Tab', () => {
|
||||
await page.fill('input[name="email"]', testEmail);
|
||||
await page.fill('input[name="password"]', testPassword);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 10000 });
|
||||
|
||||
// Navigate to schedule page
|
||||
await page.goto(`/admin/tournaments/${tournamentId}/schedule`);
|
||||
@@ -166,7 +166,7 @@ test.describe.skip('Issue #7: Schedule Tab', () => {
|
||||
await page.fill('input[name="email"]', testEmail);
|
||||
await page.fill('input[name="password"]', testPassword);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 10000 });
|
||||
|
||||
// Navigate to schedule page
|
||||
await page.goto(`/admin/tournaments/${tournamentId}/schedule`);
|
||||
@@ -175,7 +175,7 @@ test.describe.skip('Issue #7: Schedule Tab', () => {
|
||||
await page.click('button:has-text("Generate Schedule")');
|
||||
|
||||
// Wait for success message or page reload
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Verify rounds were created in database
|
||||
const rounds = await prisma.tournamentRound.findMany({
|
||||
@@ -196,7 +196,7 @@ test.describe.skip('Issue #7: Schedule Tab', () => {
|
||||
await page.fill('input[name="email"]', testEmail);
|
||||
await page.fill('input[name="password"]', testPassword);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 10000 });
|
||||
|
||||
// Navigate to schedule page
|
||||
await page.goto(`/admin/tournaments/${tournamentId}/schedule`);
|
||||
@@ -218,7 +218,7 @@ test.describe.skip('Issue #7: Schedule Tab', () => {
|
||||
await page.fill('input[name="email"]', testEmail);
|
||||
await page.fill('input[name="password"]', testPassword);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 10000 });
|
||||
|
||||
// Call the schedule API
|
||||
const response = await page.request.get(
|
||||
|
||||
@@ -18,7 +18,7 @@ function getTestCredentials() {
|
||||
};
|
||||
}
|
||||
|
||||
test.describe.skip('Issue #22: Team Configuration', () => {
|
||||
test.describe.serial('Issue #22: Team Configuration', () => {
|
||||
let testEmail: string;
|
||||
let testPassword: string;
|
||||
let tournamentId: number;
|
||||
@@ -82,7 +82,7 @@ test.describe.skip('Issue #22: Team Configuration', () => {
|
||||
await page.fill('input[name="email"]', testEmail);
|
||||
await page.fill('input[name="password"]', testPassword);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 10000 });
|
||||
|
||||
// Navigate to tournament creation
|
||||
await page.goto('/admin/tournaments/new');
|
||||
@@ -105,7 +105,7 @@ test.describe.skip('Issue #22: Team Configuration', () => {
|
||||
await page.fill('input[name="email"]', testEmail);
|
||||
await page.fill('input[name="password"]', testPassword);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 10000 });
|
||||
|
||||
// Navigate to tournament creation
|
||||
await page.goto('/admin/tournaments/new');
|
||||
@@ -126,8 +126,7 @@ test.describe.skip('Issue #22: Team Configuration', () => {
|
||||
const playerName3 = `Player ${Date.now() + 2}`;
|
||||
const playerName4 = `Player ${Date.now() + 3}`;
|
||||
|
||||
// Create first player - wait for search input to appear first
|
||||
await page.waitForSelector('input[placeholder*="Search"]', { timeout: 5000 });
|
||||
// Create first player
|
||||
await page.fill('input[placeholder*="Search"]', playerName1);
|
||||
await page.waitForTimeout(500);
|
||||
await page.click(`text=+ Create "${playerName1}" as new player`);
|
||||
@@ -181,7 +180,7 @@ test.describe.skip('Issue #22: Team Configuration', () => {
|
||||
await page.fill('input[name="email"]', testEmail);
|
||||
await page.fill('input[name="password"]', testPassword);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 10000 });
|
||||
|
||||
// Navigate to tournament creation
|
||||
await page.goto('/admin/tournaments/new');
|
||||
@@ -259,7 +258,7 @@ test.describe.skip('Issue #22: Team Configuration', () => {
|
||||
await page.fill('input[name="email"]', testEmail);
|
||||
await page.fill('input[name="password"]', testPassword);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 10000 });
|
||||
|
||||
// Navigate to edit tournament page
|
||||
await page.goto(`/admin/tournaments/${tournamentId}/edit`);
|
||||
|
||||
@@ -29,7 +29,7 @@ function getTestCredentials() {
|
||||
};
|
||||
}
|
||||
|
||||
test.describe.skip('Tournament with 10 Participants and Variable Team Durability', () => {
|
||||
test.describe.serial('Tournament with 10 Participants and Variable Team Durability', () => {
|
||||
let testEmail: string;
|
||||
let testPassword: string;
|
||||
let tournamentId: number;
|
||||
@@ -189,7 +189,7 @@ test.describe.skip('Tournament with 10 Participants and Variable Team Durability
|
||||
await page.click('button:has-text("Add")');
|
||||
|
||||
// Wait for the player to be added and UI to update
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(1000);
|
||||
}
|
||||
|
||||
// Verify 10 players are added
|
||||
|
||||
+7
-77
@@ -11,54 +11,11 @@
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
const BASE_URL = process.env.CI ? 'https://euchre-ci.notsosm.art' : 'http://localhost:3000';
|
||||
|
||||
function getTestCredentials() {
|
||||
const timestamp = Date.now();
|
||||
return {
|
||||
email: `allowties-admin-${timestamp}@example.com`,
|
||||
password: 'AdminPassword123!',
|
||||
name: `AllowTies Admin ${timestamp}`,
|
||||
};
|
||||
}
|
||||
|
||||
test.describe.skip('Tournament Edit - allowTies functionality', () => {
|
||||
test.describe('Tournament Edit - allowTies functionality', () => {
|
||||
let tournamentId: number;
|
||||
let testEmail: string;
|
||||
let testPassword: string;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
// Create admin user via API
|
||||
const credentials = getTestCredentials();
|
||||
testEmail = credentials.email;
|
||||
testPassword = credentials.password;
|
||||
|
||||
const response = await fetch(`${BASE_URL}/api/auth/sign-up/email`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Origin: BASE_URL,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: testEmail,
|
||||
password: testPassword,
|
||||
name: credentials.name,
|
||||
}),
|
||||
});
|
||||
|
||||
console.log('allowTies test user creation response:', response.status);
|
||||
|
||||
// Update user to club_admin role
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: testEmail },
|
||||
});
|
||||
if (user) {
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { role: 'club_admin' },
|
||||
});
|
||||
}
|
||||
|
||||
// Create a test tournament for editing
|
||||
const tournament = await prisma.event.create({
|
||||
data: {
|
||||
@@ -68,7 +25,6 @@ test.describe.skip('Tournament Edit - allowTies functionality', () => {
|
||||
status: 'planned',
|
||||
allowTies: false,
|
||||
targetScore: 5,
|
||||
ownerId: user?.id,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
@@ -81,28 +37,16 @@ test.describe.skip('Tournament Edit - allowTies functionality', () => {
|
||||
if (tournamentId) {
|
||||
await prisma.event.delete({
|
||||
where: { id: tournamentId },
|
||||
}).catch(() => {});
|
||||
}
|
||||
// Clean up user
|
||||
const user = await prisma.user.findUnique({ where: { email: testEmail } });
|
||||
if (user) {
|
||||
await prisma.user.delete({ where: { id: user.id } });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('should display allowTies checkbox on edit form @chromium-admin', async ({ page }) => {
|
||||
// Login first
|
||||
await page.goto('/auth/login');
|
||||
await page.fill('input[name="email"]', testEmail);
|
||||
await page.fill('input[name="password"]', testPassword);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
|
||||
|
||||
// Navigate to tournament edit page
|
||||
await page.goto(`/admin/tournaments/${tournamentId}/edit`);
|
||||
|
||||
// Wait for form to load - edit page shows "Edit Tournament" heading
|
||||
await expect(page.locator('text=Edit Tournament')).toBeVisible({ timeout: 5000 });
|
||||
// Wait for form to load
|
||||
await expect(page.locator('text=Tournament Name')).toBeVisible();
|
||||
|
||||
// Check that allowTies checkbox exists
|
||||
const allowTiesCheckbox = page.locator('input[name="allowTies"]');
|
||||
@@ -111,18 +55,11 @@ test.describe.skip('Tournament Edit - allowTies functionality', () => {
|
||||
});
|
||||
|
||||
test('should save allowTies when toggled to true @chromium-admin', async ({ page }) => {
|
||||
// Login first
|
||||
await page.goto('/auth/login');
|
||||
await page.fill('input[name="email"]', testEmail);
|
||||
await page.fill('input[name="password"]', testPassword);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
|
||||
|
||||
// Navigate to tournament edit page
|
||||
await page.goto(`/admin/tournaments/${tournamentId}/edit`);
|
||||
|
||||
// Wait for form to load
|
||||
await expect(page.locator('text=Edit Tournament')).toBeVisible({ timeout: 5000 });
|
||||
await expect(page.locator('text=Tournament Name')).toBeVisible();
|
||||
|
||||
// Toggle allowTies checkbox
|
||||
const allowTiesCheckbox = page.locator('input[name="allowTies"]');
|
||||
@@ -150,18 +87,11 @@ test.describe.skip('Tournament Edit - allowTies functionality', () => {
|
||||
data: { allowTies: true },
|
||||
});
|
||||
|
||||
// Login first
|
||||
await page.goto('/auth/login');
|
||||
await page.fill('input[name="email"]', testEmail);
|
||||
await page.fill('input[name="password"]', testPassword);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
|
||||
|
||||
// Navigate to tournament edit page
|
||||
await page.goto(`/admin/tournaments/${tournamentId}/edit`);
|
||||
|
||||
// Wait for form to load
|
||||
await expect(page.locator('text=Edit Tournament')).toBeVisible({ timeout: 5000 });
|
||||
await expect(page.locator('text=Tournament Name')).toBeVisible();
|
||||
|
||||
// Verify checkbox is checked
|
||||
const allowTiesCheckbox = page.locator('input[name="allowTies"]');
|
||||
@@ -184,4 +114,4 @@ test.describe.skip('Tournament Edit - allowTies functionality', () => {
|
||||
|
||||
expect(updatedTournament?.allowTies).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -259,12 +259,12 @@ deploy-prod version:
|
||||
docker compose pull app && \
|
||||
docker compose up -d app && \
|
||||
echo "Waiting for production site to be healthy..." && \
|
||||
for i in {1..6}; do \
|
||||
for i in {1..30}; do \
|
||||
if curl -sf https://euchre.notsosm.art/api/health > /dev/null 2>&1; then \
|
||||
echo "✅ Production successfully deployed with version {{version}}"; \
|
||||
exit 0; \
|
||||
fi; \
|
||||
sleep 15; \
|
||||
sleep 0.5; \
|
||||
done && \
|
||||
echo "❌ Production deployment failed - health check timed out"; \
|
||||
docker compose logs app; \
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "euchre_camp",
|
||||
"version": "0.1.21",
|
||||
"version": "0.1.20",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "NEXT_PUBLIC_GIT_COMMIT=$(git rev-parse --short HEAD) next dev",
|
||||
|
||||
@@ -4,12 +4,16 @@ export default defineConfig({
|
||||
testDir: './e2e',
|
||||
timeout: 30000,
|
||||
expect: {
|
||||
timeout: 2000
|
||||
timeout: 5000
|
||||
},
|
||||
// Run tests in parallel for speed - database isolation is per-test via unique data
|
||||
fullyParallel: true,
|
||||
// Use multiple workers in CI to speed up test execution
|
||||
workers: process.env.CI ? 10 : 1,
|
||||
// Run tests sequentially to avoid database conflicts
|
||||
fullyParallel: false,
|
||||
// Fail the build on CI if you accidentally left test.only in the source code.
|
||||
forbidOnly: !!process.env.CI,
|
||||
// Retry on CI only.
|
||||
retries: process.env.CI ? 1 : 0,
|
||||
// Use 1 worker in CI to avoid database conflicts between parallel projects
|
||||
workers: 1,
|
||||
// Reporter to use
|
||||
reporter: 'html',
|
||||
// Global setup and teardown
|
||||
|
||||
Reference in New Issue
Block a user