@lakutata/template-service 3.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +9 -0
- package/features/i18n/packages/app/package.json +6 -0
- package/features/i18n/packages/app/src/locales/en.json +5 -0
- package/features/i18n/packages/app/src/locales/zh-CN.json +5 -0
- package/features/installer/Dockerfile.installer +10 -0
- package/features/installer/package.json +9 -0
- package/features/installer/packages/app/package.json +26 -0
- package/features/installer/packages/resources/logo.png +0 -0
- package/features/installer/packages/resources/package.json +6 -0
- package/package.json +23 -0
- package/template/Dockerfile +18 -0
- package/template/README.md +71 -0
- package/template/_dockerignore +10 -0
- package/template/_gitignore +13 -0
- package/template/package.json +30 -0
- package/template/packages/app/package.json +27 -0
- package/template/packages/app/src/App.ts +7 -0
- package/template/packages/app/src/config/Config.ts +47 -0
- package/template/packages/app/src/controllers/ExampleController.ts +32 -0
- package/template/packages/app/src/controllers/HealthController.ts +13 -0
- package/template/packages/app/src/dtos/GreetDTO.ts +7 -0
- package/template/packages/app/src/middlewares/Authenticate.ts +22 -0
- package/template/packages/app/src/tests/App.spec.ts +46 -0
- package/template/packages/app/tsconfig.json +9 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-present Lakutata
|
|
4
|
+
|
|
5
|
+
All rights reserved
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# @lakutata/template-service
|
|
2
|
+
|
|
3
|
+
The service template of the [lakutata](https://github.com/lakutata/lakutata) projects: an HTTP API, deployed as a Docker
|
|
4
|
+
image. The project is a workspace whose `app` package is the service; other packages (a client SDK…) are added next to
|
|
5
|
+
it.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @lakutata/cli create --name my-service --template service
|
|
9
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# The installers of Linux (npm run package:linux), made on Linux: the packages of the configuration (lakutata.installer
|
|
2
|
+
# of the package.json) in dist/installer. The ones of another architecture: docker buildx build --platform linux/amd64 ...
|
|
3
|
+
# The apk packages are made on Alpine (node:22-alpine), with Bun when the application has native addons.
|
|
4
|
+
FROM node:22-bookworm AS build
|
|
5
|
+
WORKDIR /project
|
|
6
|
+
COPY . .
|
|
7
|
+
RUN (npm ci --no-audit --no-fund || npm install --no-audit --no-fund) && npm run build && npm run package -w app -- --outdir /installer
|
|
8
|
+
|
|
9
|
+
FROM scratch
|
|
10
|
+
COPY --from=build /installer /
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scripts": {
|
|
3
|
+
"package:host": "npm run build && npm run package -w app -- --outdir ../../dist/installer",
|
|
4
|
+
"package:linux": "docker buildx build --file Dockerfile.installer --output type=local,dest=dist/installer ."
|
|
5
|
+
},
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"@lakutata/builder": "^{$LAKUTATA_VERSION}"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"bin": {
|
|
3
|
+
"{$APP_ID}": "build/App.js"
|
|
4
|
+
},
|
|
5
|
+
"scripts": {
|
|
6
|
+
"package": "lakutata package installer"
|
|
7
|
+
},
|
|
8
|
+
"lakutata": {
|
|
9
|
+
"icon": "../resources/logo.png",
|
|
10
|
+
"installer": {
|
|
11
|
+
"name": "{$APP_ID}",
|
|
12
|
+
"service": {
|
|
13
|
+
"env": {
|
|
14
|
+
"PORT": "8080"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"linux": {
|
|
18
|
+
"targets": [
|
|
19
|
+
"deb",
|
|
20
|
+
"rpm",
|
|
21
|
+
"tar.gz"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lakutata/template-service",
|
|
3
|
+
"version": "3.0.0-beta.0",
|
|
4
|
+
"description": "The service template of the lakutata projects: an HTTP API deployed as a Docker image.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"lakutata",
|
|
7
|
+
"lakutata-template"
|
|
8
|
+
],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"files": [
|
|
11
|
+
"template",
|
|
12
|
+
"features",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"lakutata": {
|
|
17
|
+
"template": "service",
|
|
18
|
+
"features": {
|
|
19
|
+
"i18n": "The messages in several languages (lakutata/com/i18n): src/locales, the language of each request",
|
|
20
|
+
"installer": "The installers of the application (lakutata package installer): deb, rpm and tar.gz on Linux (npm run package:linux in Docker), pkg and tar.gz on macOS, nsis, msi and zip on Windows, its service registered (systemd, launchd, WinSW), its program in the PATH"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# The build: the dependencies and the sources, compiled
|
|
2
|
+
FROM node:22-alpine AS build
|
|
3
|
+
WORKDIR /project
|
|
4
|
+
COPY . .
|
|
5
|
+
RUN npm ci --no-audit --no-fund && npm run build && npm prune --omit=dev
|
|
6
|
+
|
|
7
|
+
# The image: the compiled service and its production dependencies, run by an unprivileged user
|
|
8
|
+
FROM node:22-alpine
|
|
9
|
+
ENV NODE_ENV=production PORT=8080
|
|
10
|
+
WORKDIR /project
|
|
11
|
+
COPY --from=build --chown=node:node /project /project
|
|
12
|
+
USER node
|
|
13
|
+
EXPOSE 8080
|
|
14
|
+
# Whether the process answers: the dependencies (database, message broker) are not part of it, the service stays up
|
|
15
|
+
# while they recover
|
|
16
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
|
17
|
+
CMD node -e "fetch('http://127.0.0.1:'+process.env.PORT+'/health').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"
|
|
18
|
+
CMD ["node", "packages/app/build/App.js"]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# {$APP_NAME}
|
|
2
|
+
|
|
3
|
+
{$APP_DESC}
|
|
4
|
+
|
|
5
|
+
A [lakutata](https://github.com/lakutata/lakutata) service. The project is a workspace: `packages/app` is the service;
|
|
6
|
+
the other packages (a client SDK…) are added in `packages/`.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm run dev # build and start the service, http://localhost:8080/example
|
|
10
|
+
npm test # build the packages and run their tests
|
|
11
|
+
npm run build # build the packages, in the order of their dependencies
|
|
12
|
+
npm run version:patch # give the packages their next version
|
|
13
|
+
npm run docker:build # build the image {$APP_ID}:<version>
|
|
14
|
+
DOCKER_REPOSITORY=registry.example.com/team/{$APP_ID} npm run docker:push # build it for linux/amd64 and push it
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The results are `{errno, errMsg, data}`, the errors too (with the status 200). The requests carry
|
|
18
|
+
`Authorization: Bearer <API_TOKEN>` when the environment has an `API_TOKEN`
|
|
19
|
+
(`packages/app/src/middlewares/Authenticate.ts`). `/health` tells whether the process answers.
|
|
20
|
+
|
|
21
|
+
`docker login <registry>` once before pushing: the credentials are kept by Docker.
|
|
22
|
+
|
|
23
|
+
<!-- @if installer -->
|
|
24
|
+
## The installers
|
|
25
|
+
|
|
26
|
+
`npm run package:host` makes the installers of the platform it runs on (`lakutata package installer`, configured by the
|
|
27
|
+
`lakutata.installer` of `packages/app/package.json`) in `dist/installer`: the application, its command
|
|
28
|
+
`{$APP_ID}` in the PATH, and its service started at the start of the system, with its environment (`PORT`, `MODE`…, kept by
|
|
29
|
+
the upgrades) and its data:
|
|
30
|
+
|
|
31
|
+
- Linux: the deb, rpm and tar.gz packages; the application in `/opt/{$APP_ID}`, its service run by systemd, its environment
|
|
32
|
+
`/etc/{$APP_ID}/{$APP_ID}.env` (`systemctl restart {$APP_ID}` after changing it), its data `/var/lib/{$APP_ID}`.
|
|
33
|
+
`npm run package:linux` makes them in Docker (`Dockerfile.installer`, in `dist/installer`) on any platform.
|
|
34
|
+
- macOS: the pkg package; the application in `/usr/local/lib/{$APP_ID}`, its service run by launchd, its environment
|
|
35
|
+
`/usr/local/etc/{$APP_ID}/{$APP_ID}.env` (`sudo /usr/local/lib/{$APP_ID}/uninstall` removes it).
|
|
36
|
+
- Windows: the installer of NSIS and the package of Windows Installer (msi); the application in
|
|
37
|
+
`C:\Program Files\{$APP_ID}`, its service run by WinSW, its environment `C:\ProgramData\{$APP_ID}\{$APP_ID}.env`
|
|
38
|
+
(`Restart-Service {$APP_ID}` after changing it).
|
|
39
|
+
|
|
40
|
+
<!-- @end -->
|
|
41
|
+
## Database migrations
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm run migration:generate # the migrations of the changes of the entities of the databases
|
|
45
|
+
npm run migration:create -- seed-users # an empty migration, written by hand
|
|
46
|
+
npm run migration:show # the migrations run [X] and pending [ ]
|
|
47
|
+
npm run migration:run # run the pending migrations
|
|
48
|
+
npm run migration:revert # revert the last migration run
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The commands (`lakutata migration <action>`) build and start the service, which connects its databases with their
|
|
52
|
+
options (without synchronizing their schemas), does the command and exits; `-- --database <name>` selects a database
|
|
53
|
+
(required to revert when there are several), `-- --fake` marks the migrations as run or reverted without executing
|
|
54
|
+
them. The migrations are written into `packages/app/src/migrations`, compiled with the service and given to the options of the
|
|
55
|
+
databases, run when they connect:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import {buildDatabaseOptions} from 'lakutata/com/database'
|
|
59
|
+
import {Migrations1788773616343} from '../migrations/1788773616343-db.js'
|
|
60
|
+
|
|
61
|
+
components: {
|
|
62
|
+
db: buildDatabaseOptions({
|
|
63
|
+
type: 'postgres',
|
|
64
|
+
url: process.env.DATABASE_URL,
|
|
65
|
+
entities: [/* the entities */],
|
|
66
|
+
synchronize: false,
|
|
67
|
+
migrationsRun: true,
|
|
68
|
+
migrations: [Migrations1788773616343]
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{$APP_ID}-workspace",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"workspaces": [
|
|
6
|
+
"packages/*"
|
|
7
|
+
],
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "lakutata run rebuild",
|
|
10
|
+
"test": "lakutata run test",
|
|
11
|
+
"dev": "lakutata run dev --parallel",
|
|
12
|
+
"migration:generate": "lakutata migration generate --package app",
|
|
13
|
+
"migration:create": "lakutata migration create --package app",
|
|
14
|
+
"migration:run": "lakutata migration run --package app",
|
|
15
|
+
"migration:revert": "lakutata migration revert --package app",
|
|
16
|
+
"migration:show": "lakutata migration show --package app",
|
|
17
|
+
"start": "npm start -w app",
|
|
18
|
+
"version:patch": "lakutata version patch --yes",
|
|
19
|
+
"docker:build": "docker buildx build --tag {$APP_ID}:$npm_package_version --load .",
|
|
20
|
+
"docker:push": "docker buildx build --platform linux/amd64 --tag $DOCKER_REPOSITORY:$npm_package_version --tag $DOCKER_REPOSITORY:latest --push ."
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@lakutata/cli": "^{$LAKUTATA_VERSION}",
|
|
24
|
+
"@types/node": "^22.0.0",
|
|
25
|
+
"typescript": "^6.0.3"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=22"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "app",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "{$APP_DESC}",
|
|
5
|
+
"author": "{$APP_AUTHOR}",
|
|
6
|
+
"license": "{$APP_LICENSE}",
|
|
7
|
+
"private": true,
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "build/App.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"clean": "lakutata clean",
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"rebuild": "npm run clean && npm run build",
|
|
14
|
+
"dev": "npm run rebuild && node build/App.js",
|
|
15
|
+
"start": "node build/App.js",
|
|
16
|
+
"test": "node --test \"build/tests/**/*.spec.js\""
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"compression": "^1.8.1",
|
|
20
|
+
"express": "^5.2.1",
|
|
21
|
+
"lakutata": "^{$LAKUTATA_VERSION}"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/compression": "^1.8.1",
|
|
25
|
+
"@types/express": "^5.0.6"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import {Application, type ILogger} from 'lakutata'
|
|
2
|
+
import {installerConfig} from './config/Config.js'
|
|
3
|
+
|
|
4
|
+
Application
|
|
5
|
+
.run(installerConfig)
|
|
6
|
+
.onLaunched((app: Application, log: ILogger): void => log.info('%s started in %s mode', app.appName, app.mode()))
|
|
7
|
+
.onFatalException((error: Error, log: ILogger): void => log.error('The service cannot start: %s', error.message))
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type {ApplicationOptions} from 'lakutata'
|
|
2
|
+
import {buildEntrypoints} from 'lakutata/com/entrypoint'
|
|
3
|
+
import {expressHTTPEntrypoint, jsonEnvelope} from 'lakutata/adapter/express'
|
|
4
|
+
import compression from 'compression'
|
|
5
|
+
import {authenticate} from '../middlewares/Authenticate.js'
|
|
6
|
+
import {ExampleController} from '../controllers/ExampleController.js'
|
|
7
|
+
import {HealthController} from '../controllers/HealthController.js'
|
|
8
|
+
//@if i18n
|
|
9
|
+
import {buildI18nOptions, I18nMiddleware} from 'lakutata/com/i18n'
|
|
10
|
+
import en from '../locales/en.json' with {type: 'json'}
|
|
11
|
+
import zhCN from '../locales/zh-CN.json' with {type: 'json'}
|
|
12
|
+
//@end
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The options of the service
|
|
16
|
+
* @param port the port of the HTTP server, the PORT environment variable or 8080 by default
|
|
17
|
+
*/
|
|
18
|
+
export function installerConfig(port: number = Number(process.env.PORT ?? 8080)): ApplicationOptions {
|
|
19
|
+
return {
|
|
20
|
+
id: '{$APP_ID}',
|
|
21
|
+
name: '{$APP_NAME}',
|
|
22
|
+
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
|
|
23
|
+
components: {
|
|
24
|
+
//@if i18n
|
|
25
|
+
//The messages of the languages (src/locales), bundled with the code; created by the first request
|
|
26
|
+
i18n: buildI18nOptions({messages: {'en': en, 'zh-CN': zhCN}}),
|
|
27
|
+
//@end
|
|
28
|
+
entrypoint: buildEntrypoints({
|
|
29
|
+
controllers: [HealthController, ExampleController],
|
|
30
|
+
//@if i18n
|
|
31
|
+
//The messages in the language of each request: ?lang=, the cookie lang, Accept-Language
|
|
32
|
+
middlewares: [I18nMiddleware],
|
|
33
|
+
//@end
|
|
34
|
+
http: expressHTTPEntrypoint({
|
|
35
|
+
port: port,
|
|
36
|
+
//The results and the errors are {errno, errMsg, data}, with the status 200
|
|
37
|
+
respond: jsonEnvelope({errorStatus: 200}),
|
|
38
|
+
body: {limit: '100mb'},
|
|
39
|
+
server: {keepAliveTimeout: 60000, headersTimeout: 65000},
|
|
40
|
+
middlewares: [authenticate],
|
|
41
|
+
before: (app): void => void app.use(compression())
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
},
|
|
45
|
+
bootstrap: ['entrypoint']
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {Controller} from 'lakutata/com/entrypoint'
|
|
2
|
+
import {GET, Prefix} from 'lakutata/decorator/ctrl'
|
|
3
|
+
import {GreetDTO} from '../dtos/GreetDTO.js'
|
|
4
|
+
//@if i18n
|
|
5
|
+
import {Inject} from 'lakutata/decorator/di'
|
|
6
|
+
import {Translator} from 'lakutata/com/i18n'
|
|
7
|
+
//@end
|
|
8
|
+
|
|
9
|
+
@Prefix({route: '/example'})
|
|
10
|
+
export class ExampleController extends Controller {
|
|
11
|
+
//@if i18n
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The messages of the language of the request
|
|
15
|
+
*/
|
|
16
|
+
@Inject(Translator)
|
|
17
|
+
protected readonly i18n: Translator
|
|
18
|
+
//@end
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* GET /example?name=you
|
|
22
|
+
* @param input
|
|
23
|
+
*/
|
|
24
|
+
@GET('/', GreetDTO)
|
|
25
|
+
public async greet(input: GreetDTO): Promise<{ message: string }> {
|
|
26
|
+
//@if i18n
|
|
27
|
+
return {message: this.i18n.t('greeting.hello', {name: input.name})}
|
|
28
|
+
//@else
|
|
29
|
+
return {message: `Hello, ${input.name}!`}
|
|
30
|
+
//@end
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {Controller} from 'lakutata/com/entrypoint'
|
|
2
|
+
import {GET} from 'lakutata/decorator/ctrl'
|
|
3
|
+
|
|
4
|
+
export class HealthController extends Controller {
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* GET /health: the process answers
|
|
8
|
+
*/
|
|
9
|
+
@GET('/health')
|
|
10
|
+
public async health(): Promise<{ status: string }> {
|
|
11
|
+
return {status: 'ok'}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {Exception} from 'lakutata'
|
|
2
|
+
import type {ActionInvocation, HTTPContext, Next} from 'lakutata/com/entrypoint'
|
|
3
|
+
|
|
4
|
+
export class UnauthorizedException extends Exception {
|
|
5
|
+
public errno: string = 'E_UNAUTHORIZED'
|
|
6
|
+
|
|
7
|
+
public readonly statusCode: number = 401
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The requests carry "Authorization: Bearer <API_TOKEN>" when the environment has an API_TOKEN (the health check excepted).
|
|
12
|
+
* Replace it with the authentication of the service (a session, a JWT verified with the keys of the identity provider…)
|
|
13
|
+
* @param invocation
|
|
14
|
+
* @param next
|
|
15
|
+
*/
|
|
16
|
+
export async function authenticate(invocation: ActionInvocation, next: Next): Promise<unknown> {
|
|
17
|
+
const token: string | undefined = process.env.API_TOKEN
|
|
18
|
+
const context: HTTPContext = invocation.context as HTTPContext
|
|
19
|
+
if (!token || context.route === '/health') return next()
|
|
20
|
+
if (context.request.headers.authorization !== `Bearer ${token}`) throw new UnauthorizedException('The request is not authenticated')
|
|
21
|
+
return next()
|
|
22
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {after, before, describe, it} from 'node:test'
|
|
2
|
+
import assert from 'node:assert'
|
|
3
|
+
import {Application} from 'lakutata'
|
|
4
|
+
import {installerConfig} from '../config/Config.js'
|
|
5
|
+
|
|
6
|
+
const PORT: number = Number(process.env.TEST_PORT ?? 18081)
|
|
7
|
+
|
|
8
|
+
describe('{$APP_NAME}', (): void => {
|
|
9
|
+
|
|
10
|
+
let app: Application
|
|
11
|
+
|
|
12
|
+
before(async (): Promise<void> => {
|
|
13
|
+
process.env.API_TOKEN = 'test-token'
|
|
14
|
+
app = await new Promise<Application>((resolve: (app: Application) => void): void => {
|
|
15
|
+
Application.run(installerConfig(PORT)).onLaunched((launched: Application): void => resolve(launched))
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
after(async (): Promise<void> => {
|
|
20
|
+
await app?.dispose()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('answers the health check without authentication', async (): Promise<void> => {
|
|
24
|
+
assert.deepStrictEqual(await (await fetch(`http://127.0.0.1:${PORT}/health`)).json(), {errno: null, errMsg: null, data: {status: 'ok'}})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('greets the authenticated requests', async (): Promise<void> => {
|
|
28
|
+
const response: Response = await fetch(`http://127.0.0.1:${PORT}/example?name=you`, {headers: {Authorization: 'Bearer test-token'}})
|
|
29
|
+
assert.deepStrictEqual(await response.json(), {errno: null, errMsg: null, data: {message: 'Hello, you!'}})
|
|
30
|
+
})
|
|
31
|
+
//@if i18n
|
|
32
|
+
|
|
33
|
+
it('greets in the language of the request, its validation errors too', async (): Promise<void> => {
|
|
34
|
+
const headers: Record<string, string> = {'Authorization': 'Bearer test-token', 'Accept-Language': 'zh-CN,zh;q=0.9'}
|
|
35
|
+
assert.deepStrictEqual(await (await fetch(`http://127.0.0.1:${PORT}/example?name=you`, {headers: headers})).json(), {errno: null, errMsg: null, data: {message: '你好,you!'}})
|
|
36
|
+
const invalid = await (await fetch(`http://127.0.0.1:${PORT}/example?name=`, {headers: headers})).json() as { errMsg: string }
|
|
37
|
+
assert.strictEqual(invalid.errMsg, '"name" 不能为空')
|
|
38
|
+
})
|
|
39
|
+
//@end
|
|
40
|
+
|
|
41
|
+
it('refuses the requests without authentication', async (): Promise<void> => {
|
|
42
|
+
const response: Response = await fetch(`http://127.0.0.1:${PORT}/example`)
|
|
43
|
+
assert.strictEqual(response.status, 200)
|
|
44
|
+
assert.deepStrictEqual(await response.json(), {errno: 'E_UNAUTHORIZED', errMsg: 'The request is not authenticated', data: null})
|
|
45
|
+
})
|
|
46
|
+
})
|