mirror of
https://github.com/immich-app/immich.git
synced 2024-12-28 06:31:58 +00:00
parent
c75adbecf3
commit
76bd603507
11 changed files with 737 additions and 0 deletions
|
@ -24,5 +24,7 @@ cli/.reverse-geocoding-dump/
|
||||||
cli/upload/
|
cli/upload/
|
||||||
cli/dist/
|
cli/dist/
|
||||||
|
|
||||||
|
e2e/
|
||||||
|
|
||||||
open-api/typescript-sdk/node_modules/
|
open-api/typescript-sdk/node_modules/
|
||||||
open-api/typescript-sdk/build/
|
open-api/typescript-sdk/build/
|
||||||
|
|
28
.github/workflows/test.yml
vendored
28
.github/workflows/test.yml
vendored
|
@ -205,6 +205,34 @@ jobs:
|
||||||
run: npm run test:cov
|
run: npm run test:cov
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
|
web-e2e-tests:
|
||||||
|
name: Web (e2e)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./e2e
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Run setup typescript-sdk
|
||||||
|
run: npm ci && npm run build
|
||||||
|
working-directory: ./open-api/typescript-sdk
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Install Playwright Browsers
|
||||||
|
run: npx playwright install --with-deps
|
||||||
|
|
||||||
|
- name: Docker build
|
||||||
|
run: docker compose -f docker/docker-compose.e2e.yml build
|
||||||
|
working-directory: ./
|
||||||
|
|
||||||
|
- name: Run e2e tests
|
||||||
|
run: npx playwright test
|
||||||
|
|
||||||
mobile-unit-tests:
|
mobile-unit-tests:
|
||||||
name: Mobile
|
name: Mobile
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
4
Makefile
4
Makefile
|
@ -22,6 +22,10 @@ server-e2e-jobs:
|
||||||
server-e2e-api:
|
server-e2e-api:
|
||||||
npm run e2e:api --prefix server
|
npm run e2e:api --prefix server
|
||||||
|
|
||||||
|
.PHONY: e2e
|
||||||
|
e2e:
|
||||||
|
docker compose -f ./docker/docker-compose.e2e.yml up --build -V --remove-orphans
|
||||||
|
|
||||||
prod:
|
prod:
|
||||||
docker compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans
|
docker compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans
|
||||||
|
|
||||||
|
|
49
docker/docker-compose.e2e.yml
Normal file
49
docker/docker-compose.e2e.yml
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
name: immich-e2e
|
||||||
|
|
||||||
|
x-server-build: &server-common
|
||||||
|
image: immich-server:latest
|
||||||
|
build:
|
||||||
|
context: ../
|
||||||
|
dockerfile: server/Dockerfile
|
||||||
|
environment:
|
||||||
|
- DB_HOSTNAME=database
|
||||||
|
- DB_USERNAME=postgres
|
||||||
|
- DB_PASSWORD=postgres
|
||||||
|
- DB_DATABASE_NAME=immich
|
||||||
|
- REDIS_HOSTNAME=redis
|
||||||
|
volumes:
|
||||||
|
- upload:/usr/src/app/upload
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
- database
|
||||||
|
|
||||||
|
services:
|
||||||
|
immich-server:
|
||||||
|
command: [ "./start.sh", "immich" ]
|
||||||
|
<<: *server-common
|
||||||
|
ports:
|
||||||
|
- 2283:3001
|
||||||
|
|
||||||
|
immich-microservices:
|
||||||
|
command: [ "./start.sh", "microservices" ]
|
||||||
|
<<: *server-common
|
||||||
|
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:6.2-alpine@sha256:51d6c56749a4243096327e3fb964a48ed92254357108449cb6e23999c37773c5
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
database:
|
||||||
|
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_DB: immich
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
model-cache:
|
||||||
|
upload:
|
5
e2e/.gitignore
vendored
Normal file
5
e2e/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules/
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/blob-report/
|
||||||
|
/playwright/.cache/
|
383
e2e/package-lock.json
generated
Normal file
383
e2e/package-lock.json
generated
Normal file
|
@ -0,0 +1,383 @@
|
||||||
|
{
|
||||||
|
"name": "immich-e2e",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "immich-e2e",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "GNU Affero General Public License version 3",
|
||||||
|
"devDependencies": {
|
||||||
|
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||||
|
"@playwright/test": "^1.41.2",
|
||||||
|
"@types/node": "^20.11.17",
|
||||||
|
"@types/pg": "^8.11.0",
|
||||||
|
"pg": "^8.11.3",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"../open-api/typescript-sdk": {
|
||||||
|
"name": "@immich/sdk",
|
||||||
|
"version": "1.92.1",
|
||||||
|
"dev": true,
|
||||||
|
"license": "GNU Affero General Public License version 3",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
|
"oazapfts": "^5.1.4",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"axios": "^1.6.7"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"axios": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@immich/sdk": {
|
||||||
|
"resolved": "../open-api/typescript-sdk",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
|
"node_modules/@playwright/test": {
|
||||||
|
"version": "1.41.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz",
|
||||||
|
"integrity": "sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"playwright": "1.41.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "20.11.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz",
|
||||||
|
"integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~5.26.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/pg": {
|
||||||
|
"version": "8.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.0.tgz",
|
||||||
|
"integrity": "sha512-sDAlRiBNthGjNFfvt0k6mtotoVYVQ63pA8R4EMWka7crawSR60waVYR0HAgmPRs/e2YaeJTD/43OoZ3PFw80pw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"pg-protocol": "*",
|
||||||
|
"pg-types": "^4.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/pg/node_modules/pg-types": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"pg-int8": "1.0.1",
|
||||||
|
"pg-numeric": "1.0.2",
|
||||||
|
"postgres-array": "~3.0.1",
|
||||||
|
"postgres-bytea": "~3.0.0",
|
||||||
|
"postgres-date": "~2.1.0",
|
||||||
|
"postgres-interval": "^3.0.0",
|
||||||
|
"postgres-range": "^1.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/pg/node_modules/postgres-array": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/pg/node_modules/postgres-bytea": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"obuf": "~1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/pg/node_modules/postgres-date": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/pg/node_modules/postgres-interval": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/buffer-writer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/obuf": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/packet-reader": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/pg": {
|
||||||
|
"version": "8.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz",
|
||||||
|
"integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-writer": "2.0.0",
|
||||||
|
"packet-reader": "1.0.0",
|
||||||
|
"pg-connection-string": "^2.6.2",
|
||||||
|
"pg-pool": "^3.6.1",
|
||||||
|
"pg-protocol": "^1.6.0",
|
||||||
|
"pg-types": "^2.1.0",
|
||||||
|
"pgpass": "1.x"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"pg-cloudflare": "^1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg-native": ">=3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"pg-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-cloudflare": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/pg-connection-string": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/pg-int8": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-numeric": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-pool": {
|
||||||
|
"version": "3.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz",
|
||||||
|
"integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==",
|
||||||
|
"dev": true,
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg": ">=8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-protocol": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/pg-types": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"pg-int8": "1.0.1",
|
||||||
|
"postgres-array": "~2.0.0",
|
||||||
|
"postgres-bytea": "~1.0.0",
|
||||||
|
"postgres-date": "~1.0.4",
|
||||||
|
"postgres-interval": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pgpass": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"split2": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright": {
|
||||||
|
"version": "1.41.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz",
|
||||||
|
"integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"playwright-core": "1.41.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright-core": {
|
||||||
|
"version": "1.41.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz",
|
||||||
|
"integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"playwright-core": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-array": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-bytea": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-date": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-interval": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"xtend": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-range": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/split2": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
||||||
|
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "5.26.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||||
|
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/xtend": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
e2e/package.json
Normal file
22
e2e/package.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "immich-e2e",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"test": "npx playwright test",
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "GNU Affero General Public License version 3",
|
||||||
|
"devDependencies": {
|
||||||
|
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||||
|
"@playwright/test": "^1.41.2",
|
||||||
|
"@types/node": "^20.11.17",
|
||||||
|
"@types/pg": "^8.11.0",
|
||||||
|
"pg": "^8.11.3",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
}
|
||||||
|
}
|
61
e2e/playwright.config.ts
Normal file
61
e2e/playwright.config.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './specs/',
|
||||||
|
fullyParallel: false,
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
workers: 1,
|
||||||
|
reporter: 'html',
|
||||||
|
use: {
|
||||||
|
baseURL: 'http://127.0.0.1:2283',
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
},
|
||||||
|
|
||||||
|
testMatch: /.*\.e2e-spec\.ts/,
|
||||||
|
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
// {
|
||||||
|
// name: 'firefox',
|
||||||
|
// use: { ...devices['Desktop Firefox'] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
// {
|
||||||
|
// name: 'webkit',
|
||||||
|
// use: { ...devices['Desktop Safari'] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against mobile viewports. */
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Chrome',
|
||||||
|
// use: { ...devices['Pixel 5'] },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Safari',
|
||||||
|
// use: { ...devices['iPhone 12'] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against branded browsers. */
|
||||||
|
// {
|
||||||
|
// name: 'Microsoft Edge',
|
||||||
|
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Google Chrome',
|
||||||
|
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
webServer: {
|
||||||
|
command:
|
||||||
|
'docker compose -f ../docker/docker-compose.e2e.yml up --build -V --remove-orphans',
|
||||||
|
url: 'http://127.0.0.1:2283',
|
||||||
|
reuseExistingServer: true,
|
||||||
|
},
|
||||||
|
});
|
81
e2e/specs/auth.e2e-spec.ts
Normal file
81
e2e/specs/auth.e2e-spec.ts
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { app } from '../test-utils';
|
||||||
|
|
||||||
|
test.describe('Registration', () => {
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
await app.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(async () => {
|
||||||
|
await app.teardown();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('admin registration', async ({ page }) => {
|
||||||
|
// welcome
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByRole('button', { name: 'Getting Started' }).click();
|
||||||
|
|
||||||
|
// register
|
||||||
|
await expect(page).toHaveTitle(/Admin Registration/);
|
||||||
|
await page.getByLabel('Admin Email').fill('admin@immich.app');
|
||||||
|
await page.getByLabel('Admin Password', { exact: true }).fill('password');
|
||||||
|
await page.getByLabel('Confirm Admin Password').fill('password');
|
||||||
|
await page.getByLabel('Name').fill('Immich Admin');
|
||||||
|
await page.getByRole('button', { name: 'Sign up' }).click();
|
||||||
|
|
||||||
|
// login
|
||||||
|
await expect(page).toHaveTitle(/Login/);
|
||||||
|
await page.goto('/auth/login');
|
||||||
|
await page.getByLabel('Email').fill('admin@immich.app');
|
||||||
|
await page.getByLabel('Password').fill('password');
|
||||||
|
await page.getByRole('button', { name: 'Login' }).click();
|
||||||
|
|
||||||
|
// onboarding
|
||||||
|
await expect(page).toHaveURL('/auth/onboarding');
|
||||||
|
await page.getByRole('button', { name: 'Theme' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Storage Template' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Done' }).click();
|
||||||
|
|
||||||
|
// success
|
||||||
|
await expect(page).toHaveURL('/photos');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user registration', async ({ context, page }) => {
|
||||||
|
await app.adminSetup(context);
|
||||||
|
|
||||||
|
// create user
|
||||||
|
await page.goto('/admin/user-management');
|
||||||
|
await expect(page).toHaveTitle(/User Management/);
|
||||||
|
await page.getByRole('button', { name: 'Create user' }).click();
|
||||||
|
await page.getByLabel('Email').fill('user@immich.cloud');
|
||||||
|
await page.getByLabel('Password', { exact: true }).fill('password');
|
||||||
|
await page.getByLabel('Confirm Password').fill('password');
|
||||||
|
await page.getByLabel('Name').fill('Immich User');
|
||||||
|
await page.getByRole('button', { name: 'Create', exact: true }).click();
|
||||||
|
|
||||||
|
// logout
|
||||||
|
await context.clearCookies();
|
||||||
|
|
||||||
|
// login
|
||||||
|
await page.goto('/auth/login');
|
||||||
|
await page.getByLabel('Email').fill('user@immich.cloud');
|
||||||
|
await page.getByLabel('Password').fill('password');
|
||||||
|
await page.getByRole('button', { name: 'Login' }).click();
|
||||||
|
|
||||||
|
// change password
|
||||||
|
expect(page.getByRole('heading')).toHaveText('Change Password');
|
||||||
|
await expect(page).toHaveURL('/auth/change-password');
|
||||||
|
await page.getByLabel('New Password').fill('new-password');
|
||||||
|
await page.getByLabel('Confirm Password').fill('new-password');
|
||||||
|
await page.getByRole('button', { name: 'Change password' }).click();
|
||||||
|
|
||||||
|
// login with new password
|
||||||
|
await expect(page).toHaveURL('/auth/login');
|
||||||
|
await page.getByLabel('Email').fill('user@immich.cloud');
|
||||||
|
await page.getByLabel('Password').fill('new-password');
|
||||||
|
await page.getByRole('button', { name: 'Login' }).click();
|
||||||
|
|
||||||
|
// success
|
||||||
|
await expect(page).toHaveURL(/\/photos/);
|
||||||
|
});
|
||||||
|
});
|
79
e2e/test-utils.ts
Normal file
79
e2e/test-utils.ts
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import pg from 'pg';
|
||||||
|
import { defaults, login, setAdminOnboarding, signUpAdmin } from '@immich/sdk';
|
||||||
|
import { BrowserContext } from '@playwright/test';
|
||||||
|
|
||||||
|
const client = new pg.Client(
|
||||||
|
'postgres://postgres:postgres@localhost:5432/immich'
|
||||||
|
);
|
||||||
|
let connected = false;
|
||||||
|
|
||||||
|
const loginCredentialDto = {
|
||||||
|
email: 'admin@immich.cloud',
|
||||||
|
password: 'password',
|
||||||
|
};
|
||||||
|
const signUpDto = { ...loginCredentialDto, name: 'Immich Admin' };
|
||||||
|
|
||||||
|
const setBaseUrl = () => (defaults.baseUrl = 'http://127.0.0.1:2283/api');
|
||||||
|
const asAuthHeader = (accessToken: string) => ({
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const app = {
|
||||||
|
adminSetup: async (context: BrowserContext) => {
|
||||||
|
setBaseUrl();
|
||||||
|
await signUpAdmin({ signUpDto });
|
||||||
|
|
||||||
|
const response = await login({ loginCredentialDto });
|
||||||
|
|
||||||
|
await context.addCookies([
|
||||||
|
{
|
||||||
|
name: 'immich_access_token',
|
||||||
|
value: response.accessToken,
|
||||||
|
domain: '127.0.0.1',
|
||||||
|
path: '/',
|
||||||
|
expires: 1742402728,
|
||||||
|
httpOnly: true,
|
||||||
|
secure: false,
|
||||||
|
sameSite: 'Lax',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'immich_auth_type',
|
||||||
|
value: 'password',
|
||||||
|
domain: '127.0.0.1',
|
||||||
|
path: '/',
|
||||||
|
expires: 1742402728,
|
||||||
|
httpOnly: true,
|
||||||
|
secure: false,
|
||||||
|
sameSite: 'Lax',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'immich_is_authenticated',
|
||||||
|
value: 'true',
|
||||||
|
domain: '127.0.0.1',
|
||||||
|
path: '/',
|
||||||
|
expires: 1742402728,
|
||||||
|
httpOnly: false,
|
||||||
|
secure: false,
|
||||||
|
sameSite: 'Lax',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await setAdminOnboarding({ headers: asAuthHeader(response.accessToken) });
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
reset: async () => {
|
||||||
|
if (!connected) {
|
||||||
|
await client.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const table of ['users', 'system_metadata']) {
|
||||||
|
await client.query(`DELETE FROM ${table} CASCADE;`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
teardown: async () => {
|
||||||
|
if (connected) {
|
||||||
|
await client.end();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
23
e2e/tsconfig.json
Normal file
23
e2e/tsconfig.json
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"strict": true,
|
||||||
|
"declaration": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"target": "es2022",
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"incremental": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"rootDirs": ["src"],
|
||||||
|
"baseUrl": "./",
|
||||||
|
"types": ["vitest/globals"]
|
||||||
|
},
|
||||||
|
"exclude": ["dist", "node_modules"]
|
||||||
|
}
|
Loading…
Reference in a new issue