Merge pull request #25 from zitadel/setup-unit-integration-tests

test: setup unit tests
This commit is contained in:
Max Peintner
2023-06-08 09:12:51 +02:00
committed by GitHub
33 changed files with 3922 additions and 1244 deletions

12
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,12 @@
### Definition of Ready
- [ ] I am happy with the code
- [ ] Short description of the feature/issue is added in the pr description
- [ ] PR is linked to the corresponding user story
- [ ] Acceptance criteria are met
- [ ] All open todos and follow ups are defined in a new ticket and justified
- [ ] Deviations from the acceptance criteria and design are agreed with the PO and documented.
- [ ] Jest unit tests ensure that components produce expected outputs on different inputs.
- [ ] Cypress integration tests ensure that login app pages work as expected. The ZITADEL API is mocked.
- [ ] No debug or dead code
- [ ] My code has no repetitions

26
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Test
on:
pull_request:
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v2
- name: Setup pnpm 7
uses: pnpm/action-setup@v2
with:
version: 7
- name: Setup Node.js 16.x
uses: actions/setup-node@v2
with:
node-version: 16.x
- name: Install Dependencies
id: deps
run: pnpm install
- name: Test
id: test
run: pnpm test

4
.gitignore vendored
View File

@@ -15,5 +15,5 @@ public/dist
.turbo .turbo
packages/zitadel-server/src/app/proto packages/zitadel-server/src/app/proto
packages/zitadel-client/src/app/proto packages/zitadel-client/src/app/proto
.vscode
apps/login/.vscode .idea

View File

@@ -38,6 +38,25 @@ However, it might be easier to develop against your ZITADEL Cloud instance
if you don't have docker installed if you don't have docker installed
or have limited resources on your local machine. or have limited resources on your local machine.
### Testing
You can execute the following commands in the following directories:
- apps/login
- packages/zitadel-client
- packages/zitadel-server
- packages/zitadel-react
- packages/zitadel-next
- The projects root directory: all tests in the project are executed
```sh
# Run all once
pnpm test
# Rerun tests on file changes
pnpm test:watch
```
### Developing Against Your Local ZITADEL Instance ### Developing Against Your Local ZITADEL Instance
```sh ```sh

View File

@@ -44,6 +44,8 @@ Each package and app is 100% [TypeScript](https://www.typescriptlang.org/).
- `pnpm generate` - Build proto stubs for server and client package - `pnpm generate` - Build proto stubs for server and client package
- `pnpm build` - Build all packages and the login app - `pnpm build` - Build all packages and the login app
- `pnpm test` - Test all packages and the login app
- `pnpm test:watch` - Rerun tests on file change
- `pnpm dev` - Develop all packages and the login app - `pnpm dev` - Develop all packages and the login app
- `pnpm lint` - Lint all packages - `pnpm lint` - Lint all packages
- `pnpm changeset` - Generate a changeset - `pnpm changeset` - Generate a changeset

View File

View File

@@ -1,4 +1,4 @@
module.exports = { module.exports = {
extends: "next/core-web-vitals", extends: ["next/core-web-vitals"],
ignorePatterns: ["external/**/*.ts"], ignorePatterns: ["external/**/*.ts"],
}; };

View File

@@ -0,0 +1,57 @@
import { render, screen, waitFor, within } from "@testing-library/react";
import PasswordComplexity from "../ui/PasswordComplexity";
// TODO: Why does this not compile?
// import { ResourceOwnerType } from '@zitadel/server';
const matchesTitle = `Matches`;
const doesntMatchTitle = `Doesn't match`;
describe("<PasswordComplexity/>", () => {
describe.each`
settingsMinLength | password | expectSVGTitle
${5} | ${"Password1!"} | ${matchesTitle}
${30} | ${"Password1!"} | ${doesntMatchTitle}
${0} | ${"Password1!"} | ${matchesTitle}
${undefined} | ${"Password1!"} | ${false}
`(
`With settingsMinLength=$settingsMinLength, password=$password, expectSVGTitle=$expectSVGTitle`,
({ settingsMinLength, password, expectSVGTitle }) => {
const feedbackElementLabel = /password length/i;
beforeEach(() => {
render(
<PasswordComplexity
password={password}
equals
passwordComplexitySettings={{
minLength: settingsMinLength,
requiresLowercase: false,
requiresUppercase: false,
requiresNumber: false,
requiresSymbol: false,
resourceOwnerType: 0, // ResourceOwnerType.RESOURCE_OWNER_TYPE_UNSPECIFIED,
}}
/>
);
});
if (expectSVGTitle === false) {
it(`should not render the feedback element`, async () => {
await waitFor(() => {
expect(
screen.queryByText(feedbackElementLabel)
).not.toBeInTheDocument();
});
});
} else {
it(`Should show one SVG with title ${expectSVGTitle}`, async () => {
await waitFor(async () => {
const svg = within(
screen.getByText(feedbackElementLabel)
.parentElement as HTMLElement
).findByRole("img");
expect(await svg).toHaveTextContent(expectSVGTitle);
});
});
}
}
);
});

View File

@@ -0,0 +1,7 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"jsx": "react-jsxdev",
"types": ["node", "jest", "@testing-library/jest-dom"]
}
}

19
apps/login/jest.config.ts Normal file
View File

@@ -0,0 +1,19 @@
import type { Config } from "@jest/types";
import { pathsToModuleNameMapper } from "ts-jest";
import { compilerOptions } from "./tsconfig.json";
export default async (): Promise<Config.InitialOptions> => {
return {
preset: "ts-jest",
transform: {
"^.+\\.tsx?$": ["ts-jest", { tsconfig: "./__test__/tsconfig.json" }],
},
setupFilesAfterEnv: ["@testing-library/jest-dom/extend-expect"],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
prefix: "<rootDir>/",
}),
testEnvironment: "jsdom",
testRegex: "/__test__/.*\\.test\\.tsx?$",
modulePathIgnorePatterns: ["cypress"],
};
};

View File

@@ -1,6 +1,6 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
// Custom hook to read auth record and user profile doc // Custom hook to read auth record and user profile doc
export function useUserData() { export function useUserData() {
const [clientData, setClientData] = useState(null); const [clientData, setClientData] = useState(null);

View File

@@ -2,13 +2,15 @@
"name": "@zitadel/login", "name": "@zitadel/login",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "next build",
"dev": "next dev", "dev": "next dev",
"test": "jest",
"test:watch": "jest --watch",
"lint": "next lint && prettier --check .", "lint": "next lint && prettier --check .",
"lint:fix": "prettier --write .", "lint:fix": "prettier --write .",
"lint-staged": "lint-staged", "lint-staged": "lint-staged",
"build": "next build",
"prestart": "build",
"start": "next start", "start": "next start",
"test": "yarn prettier:check &nexarn lint",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next" "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
}, },
"git": { "git": {
@@ -39,24 +41,34 @@
}, },
"devDependencies": { "devDependencies": {
"@bufbuild/buf": "^1.14.0", "@bufbuild/buf": "^1.14.0",
"@jest/types": "^29.5.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@types/jest": "^29.5.1",
"@types/ms": "0.7.31", "@types/ms": "0.7.31",
"@types/node": "18.11.9", "@types/node": "18.11.9",
"@types/react": "18.0.25", "@types/react": "18.2.8",
"@types/react-dom": "18.0.9", "@types/react-dom": "18.0.9",
"@types/testing-library__jest-dom": "^5.14.6",
"@types/tinycolor2": "1.4.3", "@types/tinycolor2": "1.4.3",
"@types/uuid": "^9.0.1",
"@vercel/git-hooks": "1.0.0", "@vercel/git-hooks": "1.0.0",
"@zitadel/tsconfig": "workspace:*", "@zitadel/tsconfig": "workspace:*",
"autoprefixer": "10.4.13", "autoprefixer": "10.4.13",
"del-cli": "5.0.0", "del-cli": "5.0.0",
"eslint-config-zitadel": "workspace:*", "eslint-config-zitadel": "workspace:*",
"grpc-tools": "1.11.3", "grpc-tools": "1.11.3",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"lint-staged": "13.0.3", "lint-staged": "13.0.3",
"make-dir-cli": "3.0.0", "make-dir-cli": "3.0.0",
"postcss": "8.4.21", "postcss": "8.4.21",
"prettier-plugin-tailwindcss": "0.1.13", "prettier-plugin-tailwindcss": "0.1.13",
"tailwindcss": "3.2.4", "tailwindcss": "3.2.4",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"ts-proto": "^1.139.0", "ts-proto": "^1.139.0",
"typescript": "4.8.4", "typescript": "5.0.4",
"zitadel-tailwind-config": "workspace:*" "zitadel-tailwind-config": "workspace:*"
} }
} }

View File

@@ -1,11 +1,17 @@
{ {
"extends": "@zitadel/tsconfig/nextjs.json", "extends": "@zitadel/tsconfig/nextjs.json",
"compilerOptions": { "compilerOptions": {
"jsx": "preserve",
"rootDir": ".",
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"#/*": ["./*"] "#/*": ["./*"]
}, },
"plugins": [{ "name": "next" }] "plugins": [
{
"name": "next"
}
]
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"]

View File

@@ -20,7 +20,9 @@ const check = (
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="w-6 h-6 las la-check text-green-500 dark:text-green-500 mr-2 text-lg" className="w-6 h-6 las la-check text-green-500 dark:text-green-500 mr-2 text-lg"
role="img"
> >
<title>Matches</title>
<path <path
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
@@ -36,7 +38,9 @@ const cross = (
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
role="img"
> >
<title>Doesn't match</title>
<path <path
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
@@ -60,12 +64,16 @@ export default function PasswordComplexity({
return ( return (
<div className="mb-4 grid grid-cols-2 gap-x-8 gap-y-2"> <div className="mb-4 grid grid-cols-2 gap-x-8 gap-y-2">
<div className="flex flex-row items-center"> {passwordComplexitySettings.minLength != undefined ? (
{hasMinLength ? check : cross} <div className="flex flex-row items-center">
<span className={desc}> {hasMinLength ? check : cross}
Password length {passwordComplexitySettings.minLength} <span className={desc}>
</span> Password length {passwordComplexitySettings.minLength}
</div> </span>
</div>
) : (
<span />
)}
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
{hasSymbol ? check : cross} {hasSymbol ? check : cross}
<span className={desc}>has Symbol</span> <span className={desc}>has Symbol</span>

View File

@@ -28,7 +28,9 @@ export default function VerifyEmailForm({ userId, code, submit }: Props) {
useEffect(() => { useEffect(() => {
if (submit && code && userId) { if (submit && code && userId) {
submitCode({ code }); // When we navigate to this page, we always want to be redirected if submit is true and the parameters are valid.
// For programmatic verification, the /verifyemail API should be used.
submitCodeAndContinue({ code });
} }
}, []); }, []);
@@ -53,12 +55,11 @@ export default function VerifyEmailForm({ userId, code, submit }: Props) {
const response = await res.json(); const response = await res.json();
setLoading(false);
if (!res.ok) { if (!res.ok) {
setLoading(false);
setError(response.details); setError(response.details);
return Promise.reject(response); return Promise.reject(response);
} else { } else {
setLoading(false);
return response; return response;
} }
} }

View File

@@ -1,16 +1,17 @@
{ {
"private": true, "private": true,
"scripts": { "scripts": {
"generate": "turbo run generate",
"build": "turbo run build", "build": "turbo run build",
"test": "turbo run test",
"test:watch": "turbo run test:watch",
"dev": "turbo run dev --no-cache --continue", "dev": "turbo run dev --no-cache --continue",
"lint": "turbo run lint", "lint": "turbo run lint",
"clean": "turbo run clean && rm -rf node_modules", "clean": "turbo run clean && rm -rf node_modules",
"format": "prettier --write \"**/*.{ts,tsx,md}\"", "format": "prettier --write \"**/*.{ts,tsx,md}\"",
"changeset": "changeset", "changeset": "changeset",
"version-packages": "changeset version", "version-packages": "changeset version",
"release": "turbo run build --filter=login^... && changeset publish", "release": "turbo run build --filter=login^... && changeset publish"
"prebuild": "turbo run generate",
"generate": "turbo run generate"
}, },
"devDependencies": { "devDependencies": {
"@changesets/cli": "^2.22.0", "@changesets/cli": "^2.22.0",
@@ -20,4 +21,4 @@
"turbo": "^1.9.8" "turbo": "^1.9.8"
}, },
"packageManager": "pnpm@7.15.0" "packageManager": "pnpm@7.15.0"
} }

View File

@@ -0,0 +1,8 @@
import type { JestConfigWithTsJest } from 'ts-jest'
const jestConfig: JestConfigWithTsJest = {
preset: 'ts-jest',
testEnvironment: 'node'
}
export default jestConfig

View File

@@ -10,20 +10,27 @@
"dist/**" "dist/**"
], ],
"scripts": { "scripts": {
"generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel",
"prebuild": "pnpm run generate",
"build": "tsup src/index.ts --format esm,cjs --dts", "build": "tsup src/index.ts --format esm,cjs --dts",
"test": "jest",
"test:watch": "jest --watch",
"dev": "tsup src/index.ts --format esm,cjs --watch --dts", "dev": "tsup src/index.ts --format esm,cjs --watch --dts",
"lint": "eslint \"src/**/*.ts*\"", "lint": "eslint \"src/**/*.ts*\"",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist", "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
"generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel"
}, },
"devDependencies": { "devDependencies": {
"@bufbuild/buf": "^1.14.0", "@bufbuild/buf": "^1.14.0",
"@types/jest": "^29.5.1",
"@zitadel/tsconfig": "workspace:*", "@zitadel/tsconfig": "workspace:*",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-zitadel": "workspace:*", "eslint-config-zitadel": "workspace:*",
"jest": "^29.5.0",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"ts-proto": "^1.139.0", "ts-proto": "^1.139.0",
"tsup": "^5.10.1", "tsup": "^5.10.1",
"typescript": "^4.5.3" "typescript": "^4.9.3"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
@@ -33,4 +40,4 @@
"nice-grpc-web": "^3.2.3", "nice-grpc-web": "^3.2.3",
"protobufjs": "^7.2.3" "protobufjs": "^7.2.3"
} }
} }

View File

@@ -0,0 +1,57 @@
import { CallOptions, ClientMiddlewareCall, Metadata, MethodDescriptor } from "nice-grpc-web";
import { authMiddleware } from "./middleware";
describe('authMiddleware', () => {
const scenarios = [
{
name: 'should add authorization if metadata is undefined',
initialMetadata: undefined,
expectedMetadata: new Metadata().set("authorization", "Bearer mock-token"),
token: "mock-token"
},
{
name: 'should add authorization if metadata exists but no authorization',
initialMetadata: new Metadata().set("other-key", "other-value"),
expectedMetadata: new Metadata().set("other-key", "other-value").set("authorization", "Bearer mock-token"),
token: "mock-token"
},
{
name: 'should not modify authorization if it already exists',
initialMetadata: new Metadata().set("authorization", "Bearer initial-token"),
expectedMetadata: new Metadata().set("authorization", "Bearer initial-token"),
token: "mock-token"
},
];
scenarios.forEach(({ name, initialMetadata, expectedMetadata, token }) => {
it(name, async () => {
const mockNext = jest.fn().mockImplementation(async function*() { });
const mockRequest = {};
const mockMethodDescriptor: MethodDescriptor = {
options: {idempotencyLevel: undefined},
path: '',
requestStream: false,
responseStream: false,
};
const mockCall: ClientMiddlewareCall<unknown, unknown> = {
method: mockMethodDescriptor,
requestStream: false,
responseStream: false,
request: mockRequest,
next: mockNext,
};
const options: CallOptions = {
metadata: initialMetadata
};
await authMiddleware(token)(mockCall, options).next();
expect(mockNext).toHaveBeenCalledTimes(1);
const actualMetadata = mockNext.mock.calls[0][1].metadata;
expect(actualMetadata?.get('authorization')).toEqual(expectedMetadata.get('authorization'));
});
});
});

View File

@@ -0,0 +1,8 @@
import type { JestConfigWithTsJest } from 'ts-jest'
const jestConfig: JestConfigWithTsJest = {
preset: 'ts-jest',
testEnvironment: 'node'
}
export default jestConfig

View File

@@ -11,16 +11,22 @@
], ],
"scripts": { "scripts": {
"build": "tsup src/index.tsx --format esm,cjs --dts --external react", "build": "tsup src/index.tsx --format esm,cjs --dts --external react",
"test": "jest",
"test:watch": "jest --watch",
"dev": "tsup src/index.tsx --format esm,cjs --watch --dts --external react", "dev": "tsup src/index.tsx --format esm,cjs --watch --dts --external react",
"lint": "eslint \"src/**/*.ts*\"", "lint": "eslint \"src/**/*.ts*\"",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist" "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.1",
"@zitadel/tsconfig": "workspace:*", "@zitadel/tsconfig": "workspace:*",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-zitadel": "workspace:*", "eslint-config-zitadel": "workspace:*",
"jest": "^29.5.0",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"tsup": "^5.10.1", "tsup": "^5.10.1",
"typescript": "^4.5.3" "typescript": "^4.9.3"
}, },
"peerDependencies": { "peerDependencies": {
"next": "^13" "next": "^13"
@@ -28,4 +34,4 @@
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
} }
} }

View File

@@ -0,0 +1,5 @@
describe('slug', () => {
it('this is not a real test', () => { })
})
export { }

View File

@@ -0,0 +1,9 @@
import type { JestConfigWithTsJest } from 'ts-jest'
const jestConfig: JestConfigWithTsJest = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
setupFilesAfterEnv: [ '@testing-library/jest-dom/extend-expect' ]
}
export default jestConfig

View File

@@ -13,23 +13,33 @@
}, },
"scripts": { "scripts": {
"build": "tsup", "build": "tsup",
"test": "jest",
"test:watch": "jest --watch",
"dev": "tsup --watch", "dev": "tsup --watch",
"lint": "eslint \"src/**/*.ts*\"", "lint": "eslint \"src/**/*.ts*\"",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist", "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
"copy-files": "cp -R ./src/public/ ./dist/" "copy-files": "cp -R ./src/public/ ./dist/"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@types/jest": "^29.5.1",
"@types/react": "^17.0.13", "@types/react": "^17.0.13",
"@types/react-dom": "^17.0.8", "@types/react-dom": "^17.0.8",
"@types/testing-library__jest-dom": "^5.14.6",
"@zitadel/tsconfig": "workspace:*", "@zitadel/tsconfig": "workspace:*",
"autoprefixer": "10.4.13", "autoprefixer": "10.4.13",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-zitadel": "workspace:*", "eslint-config-zitadel": "workspace:*",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"postcss": "8.4.21", "postcss": "8.4.21",
"sass": "^1.62.0", "sass": "^1.62.0",
"tailwindcss": "3.2.4", "tailwindcss": "3.2.4",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"tsup": "^5.10.1", "tsup": "^5.10.1",
"typescript": "^4.5.3", "typescript": "^4.9.3",
"zitadel-tailwind-config": "workspace:*" "zitadel-tailwind-config": "workspace:*"
}, },
"publishConfig": { "publishConfig": {
@@ -38,4 +48,4 @@
"peerDependencies": { "peerDependencies": {
"react": "18.2.0" "react": "18.2.0"
} }
} }

View File

@@ -0,0 +1,15 @@
import { render, screen } from '@testing-library/react';
import { SignInWithGoogle } from './SignInWithGoogle';
describe('<SignInWithGoogle />', () => {
it('renders without crashing', () => {
const { container } = render(<SignInWithGoogle />);
expect(container.firstChild).toBeDefined();
});
it('displays the correct text', () => {
render(<SignInWithGoogle />);
const signInText = screen.getByText(/Sign in with Google/i);
expect(signInText).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,15 @@
import { render, screen } from '@testing-library/react';
import { SignInWithGitlab } from './SignInWithGitlab';
describe('<SignInWithGitlab />', () => {
it('renders without crashing', () => {
const { container } = render(<SignInWithGitlab />);
expect(container.firstChild).toBeDefined();
});
it('displays the correct text', () => {
render(<SignInWithGitlab />);
const signInText = screen.getByText(/Sign in with Gitlab/i);
expect(signInText).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,8 @@
import type { JestConfigWithTsJest } from 'ts-jest'
const jestConfig: JestConfigWithTsJest = {
preset: 'ts-jest',
testEnvironment: 'node'
}
export default jestConfig

View File

@@ -11,21 +11,27 @@
"dist/**" "dist/**"
], ],
"scripts": { "scripts": {
"generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel",
"prebuild": "pnpm run generate",
"build": "tsup --dts", "build": "tsup --dts",
"test": "jest",
"test:watch": "jest --watch",
"dev": "tsup --dts --watch", "dev": "tsup --dts --watch",
"lint": "eslint \"src/**/*.ts*\"", "lint": "eslint \"src/**/*.ts*\"",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist", "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
"prebuild": "pnpm run generate",
"generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel"
}, },
"devDependencies": { "devDependencies": {
"@bufbuild/buf": "^1.14.0", "@bufbuild/buf": "^1.14.0",
"@types/jest": "^29.5.1",
"@zitadel/tsconfig": "workspace:*", "@zitadel/tsconfig": "workspace:*",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-zitadel": "workspace:*", "eslint-config-zitadel": "workspace:*",
"jest": "^29.5.0",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"ts-proto": "^1.139.0", "ts-proto": "^1.139.0",
"tsup": "^5.10.1", "tsup": "^5.10.1",
"typescript": "^4.5.3" "typescript": "^4.9.3"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
@@ -37,4 +43,4 @@
"nice-grpc-common": "^2.0.2", "nice-grpc-common": "^2.0.2",
"protobufjs": "^7.2.3" "protobufjs": "^7.2.3"
} }
} }

View File

@@ -32,6 +32,7 @@ export {
export { type LegalAndSupportSettings } from "./proto/server/zitadel/settings/v2alpha/legal_settings"; export { type LegalAndSupportSettings } from "./proto/server/zitadel/settings/v2alpha/legal_settings";
export { type PasswordComplexitySettings } from "./proto/server/zitadel/settings/v2alpha/password_settings"; export { type PasswordComplexitySettings } from "./proto/server/zitadel/settings/v2alpha/password_settings";
export { type ResourceOwnerType } from "./proto/server/zitadel/settings/v2alpha/settings";
import { import {
getServers, getServers,

View File

@@ -0,0 +1,57 @@
import { CallOptions, ClientMiddlewareCall, Metadata, MethodDescriptor } from "nice-grpc";
import { authMiddleware } from "./middleware";
describe('authMiddleware', () => {
const scenarios = [
{
name: 'should add authorization if metadata is undefined',
initialMetadata: undefined,
expectedMetadata: new Metadata().set("authorization", "Bearer mock-token"),
token: "mock-token"
},
{
name: 'should add authorization if metadata exists but no authorization',
initialMetadata: new Metadata().set("other-key", "other-value"),
expectedMetadata: new Metadata().set("other-key", "other-value").set("authorization", "Bearer mock-token"),
token: "mock-token"
},
{
name: 'should not modify authorization if it already exists',
initialMetadata: new Metadata().set("authorization", "Bearer initial-token"),
expectedMetadata: new Metadata().set("authorization", "Bearer initial-token"),
token: "mock-token"
},
];
scenarios.forEach(({ name, initialMetadata, expectedMetadata, token }) => {
it(name, async () => {
const mockNext = jest.fn().mockImplementation(async function*() { });
const mockRequest = {};
const mockMethodDescriptor: MethodDescriptor = {
options: {idempotencyLevel: undefined},
path: '',
requestStream: false,
responseStream: false,
};
const mockCall: ClientMiddlewareCall<unknown, unknown> = {
method: mockMethodDescriptor,
requestStream: false,
responseStream: false,
request: mockRequest,
next: mockNext,
};
const options: CallOptions = {
metadata: initialMetadata
};
await authMiddleware(token)(mockCall, options).next();
expect(mockNext).toHaveBeenCalledTimes(1);
const actualMetadata = mockNext.mock.calls[0][1].metadata;
expect(actualMetadata?.get('authorization')).toEqual(expectedMetadata.get('authorization'));
});
});
});

View File

@@ -1,7 +1,7 @@
import { CallOptions, ClientMiddlewareCall, Metadata } from "nice-grpc"; import { CallOptions, ClientMiddleware, ClientMiddlewareCall, Metadata } from "nice-grpc";
export const authMiddleware = (token: string) => export function authMiddleware (token: string): ClientMiddleware {
async function* <Request, Response>( return async function* <Request, Response>(
call: ClientMiddlewareCall<Request, Response>, call: ClientMiddlewareCall<Request, Response>,
options: CallOptions options: CallOptions
) { ) {
@@ -12,6 +12,7 @@ export const authMiddleware = (token: string) =>
return yield* call.next(call.request, options); return yield* call.next(call.request, options);
}; };
}
export const orgMetadata = (orgId: string) => export const orgMetadata = (orgId: string) =>
new Metadata({ "x-zitadel-orgid": orgId }); new Metadata({ "x-zitadel-orgid": orgId });

4663
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,16 +2,34 @@
"$schema": "https://turbo.build/schema.json", "$schema": "https://turbo.build/schema.json",
"pipeline": { "pipeline": {
"generate": { "generate": {
"outputs": ["src/proto/**"], "outputs": [
"src/proto/**"
],
"cache": true "cache": true
}, },
"build": { "build": {
"outputs": ["dist/**", ".next/**", "!.next/cache/**"], "outputs": [
"dependsOn": ["generate", "^build"] "dist/**",
".next/**",
"!.next/cache/**"
],
"dependsOn": [
"lint",
"generate",
"^build"
]
}, },
"test": { "test": {
"outputs": ["coverage/**"], "dependsOn": [
"dependsOn": [] "generate",
"@zitadel/server#build"
]
},
"test:watch": {
"dependsOn": [
"generate",
"@zitadel/server#build"
]
}, },
"lint": {}, "lint": {},
"dev": { "dev": {
@@ -22,6 +40,16 @@
"cache": false "cache": false
} }
}, },
"globalDependencies": ["**/.env.*local"], "globalDependencies": [
"globalEnv": ["ZITADEL_API_URL", "ZITADEL_SERVICE_USER_TOKEN"] "**/.env.*local"
} ],
"globalEnv": [
"ZITADEL_API_URL",
"ZITADEL_SERVICE_USER_TOKEN",
"ZITADEL_SYSTEM_API_URL",
"ZITADEL_SYSTEM_API_USERID",
"ZITADEL_SYSTEM_API_KEY",
"ZITADEL_ISSUER",
"ZITADEL_ADMIN_TOKEN"
]
}