Skip to content

Commit

Permalink
Refactor (#10)
Browse files Browse the repository at this point in the history
* separation of concerns, add many tests

* report coverage?

* use correct env path in e2e test

* spelling

* correct dir

* enable gh actions reporter

* update reporter per docs

* more dep prs
  • Loading branch information
ashmortar authored Aug 9, 2024
1 parent 00711f1 commit 08e5bb8
Show file tree
Hide file tree
Showing 30 changed files with 679 additions and 214 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ lerna-debug.log*
# Tests
/coverage
/.nyc_output
/coverage-e2e

# IDEs and editors
/.idea
Expand Down
33 changes: 22 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@
"start:debug": "npm run build:css && nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "xss-scan && jest",
"test:watch": "xss-scan && jest --watch",
"test:cov": "xss-scan && jest --coverage",
"test:debug": "xss-scan && node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "xss-scan && jest --config ./test/jest-e2e.json",
"test": "xss-scan && NODE_ENV=test jest",
"test:watch": "xss-scan && NODE_ENV=test jest --watch",
"test:cov": "xss-scan && NODE_ENV=test jest --coverage",
"test:debug": "xss-scan && NODE_ENV=test node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"migrate:dev": "prisma migrate dev",
"migrate:dev:create": "prisma migrate dev --create-only",
"migrate:deploy": "prisma migrate deploy",
Expand Down Expand Up @@ -116,6 +115,15 @@
},
"browserslist": "last 2 versions",
"jest": {
"reporters": [
[
"github-actions",
{
"silent": false
}
],
"summary"
],
"moduleFileExtensions": [
"js",
"json",
Expand All @@ -124,22 +132,25 @@
"tsx"
],
"coveragePathIgnorePatterns": [
"\\.module\\.ts",
"main.ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"rootDir": ".",
"testRegex": [
"src/.*\\.spec\\.ts$",
"test/.*\\.e2e-spec\\.ts$"
],
"transform": {
"^.+\\.(t|j)s$": "ts-jest",
"\\.[jt]sx?$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
"src/**/*.(t|j)s",
"src/**/*.(t|j)sx"
],
"coverageDirectory": "../coverage",
"coverageDirectory": "./coverage",
"testEnvironment": "node",
"moduleNameMapper": {
"@core/(.*)": "<rootDir>/$1",
"@core/(.*)": "<rootDir>/src/$1",
"@generated/(.*)": "<rootDir>/generated/$1"
}
},
Expand Down
2 changes: 1 addition & 1 deletion renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
":pinAllExceptPeerDependencies",
"npm:unpublishSafe"
],
"prConcurrentLimit": 1,
"prConcurrentLimit": 3,
"labels": ["dependencies"]
}
101 changes: 95 additions & 6 deletions src/app.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { ConfigModule } from '@nestjs/config';
import opts from './config/app';
import { isHtmlDocument, isHtmlFragment } from './htmx/htmx.utils';
import { I18nModule } from 'nestjs-i18n';
import i18n_opts from './config/i18n';

describe('AppController', () => {
let module: TestingModule;
let controller: AppController;
const SHOULD_MATCH = [
'<main id="main"',
'<h1',
'<p',
'/p>',
'/h1>',
'/main>',
];
const SHOULD_NOT_MATCH = ['<html', '<head', '<body', '<!DOCTYPE html'];

beforeAll(async () => {
module = await Test.createTestingModule({
Expand All @@ -30,10 +38,91 @@ describe('AppController', () => {
expect(controller).toBeDefined();
});

it('should return an html fragment injected with app title', () => {
const result = controller.index();
expect(isHtmlFragment(result)).toBe(true);
expect(isHtmlDocument(result)).toBe(false);
expect(result).toMatch(/NestJsx/);
describe('index', () => {
it('should return the root main content', () => {
const result = controller.index();
// haves
[...SHOULD_MATCH, 'Welcome to NestJsx', 'is a starter template'].forEach(
(el) => {
expect(result).toMatch(el);
},
);
// have-nots
SHOULD_NOT_MATCH.forEach((el) => {
expect(result).not.toMatch(el);
});
});
});

describe('about', () => {
it('should return the about main content', () => {
const result = controller.about();
// haves
[
...SHOULD_MATCH,
'About',
'This is an example of a server rendered page',
].forEach((el) => {
expect(result).toMatch(el);
});
// have-nots
SHOULD_NOT_MATCH.forEach((el) => {
expect(result).not.toMatch(el);
});
});
});

describe('contact', () => {
it('should return the contact main content', () => {
const result = controller.contact();
// haves
[
...SHOULD_MATCH,
'Contact',
'This is an example of a server rendered',
].forEach((el) => {
expect(result).toMatch(el);
});
// have-nots
SHOULD_NOT_MATCH.forEach((el) => {
expect(result).not.toMatch(el);
});
});
});

describe('privacy', () => {
it('should return the privacy main content', () => {
const result = controller.privacy();
// haves
[
...SHOULD_MATCH,
'Privacy',
'This is an example of a server rendered',
].forEach((el) => {
expect(result).toMatch(el);
});
// have-nots
SHOULD_NOT_MATCH.forEach((el) => {
expect(result).not.toMatch(el);
});
});
});

describe('terms', () => {
it('should return the terms main content', () => {
const result = controller.terms();
// haves
[
...SHOULD_MATCH,
'Terms',
'This is an example of a server rendered',
].forEach((el) => {
expect(result).toMatch(el);
});
// have-nots
SHOULD_NOT_MATCH.forEach((el) => {
expect(result).not.toMatch(el);
});
});
});
});
4 changes: 0 additions & 4 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ import opts from './config/app';
import { AppController } from './app.controller';
import { AuthModule } from './auth/auth.module';
import { I18nModule } from 'nestjs-i18n';
import { UsersModule } from './users/users.module';
import { ValidationModule } from './validation/validation.module';
import i18n_opts from './config/i18n';
import { LoggerModule } from 'nestjs-pino';
import pinoOpts from './config/pino';
import { OpenTelemetryModule } from 'nestjs-otel';
import { CredentialsService } from './credentials/credentials.service';

@Module({
controllers: [AppController],
Expand All @@ -36,9 +34,7 @@ import { CredentialsService } from './credentials/credentials.service';
PrismaModule.forRoot(),
I18nModule.forRoot(i18n_opts),
AuthModule,
UsersModule,
ValidationModule,
],
providers: [CredentialsService],
})
export class AppModule {}
110 changes: 107 additions & 3 deletions src/auth/auth.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ import { AuthController } from './auth.controller';
import { ConfigModule } from '@nestjs/config';
import { AuthService } from './auth.service';
import { JwtModule } from '@nestjs/jwt';
import { UsersService } from '@core/users/users.service';
import { PrismaService } from 'nestjs-prisma';
import { prismaMock } from '../../prisma/singleton';
import { I18nModule } from 'nestjs-i18n';
import i18n_opts from '@core/config/i18n';
import opts from '@core/config/app';
import { CredentialsService } from '@core/credentials/credentials.service';
import {
CredentialsService,
CredentialWithUserPii,
} from '@core/credentials/credentials.service';
import { SessionService } from '@core/session/session.service';
import { addDays } from 'date-fns';
import { mockRequest } from '../../test/utils';

describe('AuthController', () => {
let module: TestingModule;
Expand All @@ -27,7 +32,7 @@ describe('AuthController', () => {
],
providers: [
AuthService,
UsersService,
SessionService,
CredentialsService,
{ provide: PrismaService, useValue: prismaMock },
],
Expand All @@ -43,4 +48,103 @@ describe('AuthController', () => {
it('should be defined', () => {
expect(controller).toBeDefined();
});

describe('avatar', () => {
it('should return an avatar if a session is provided', async () => {
const result = await controller.avatar({
id: '1',
created_at: new Date(),
updated_at: new Date(),
user_id: '1',
user: {
id: '1',
created_at: new Date(),
updated_at: new Date(),
status: 'verified',
pii: [
{
id: '1',
created_at: new Date(),
updated_at: new Date(),
user_id: '1',
type: 'email',
value: '[email protected]',
},
],
},
});

expect(result).toMatch(/<a/);
expect(result).toMatch(/hx-get="\/profile" hx-target="#main"/);
expect(result).toMatch(/<div>me@email.com<\/div>/);
});
it('should return auth links if no session is provided', async () => {
const result = await controller.avatar();
expect(result).toMatch(/<nav id="auth-nav"/);
expect(result).toMatch(/hx-get="\/auth\/sign-in" hx-target="#main"/);
expect(result).toMatch(/hx-get="\/auth\/register" hx-target="#main"/);
});
});

describe('signIn', () => {
const email = '[email protected]';
const password = 'password';
const credential: CredentialWithUserPii = {
id: '1',
created_at: new Date(),
updated_at: new Date(),
user_id: '1',
external_id: email,
expires_at: addDays(new Date(), 1),
refresh_token: null,
type: 'password',
value: 'value',
user: {
id: '1',
created_at: new Date(),
updated_at: new Date(),
status: 'verified',
pii: [
{
id: '1',
created_at: new Date(),
updated_at: new Date(),
user_id: '1',
type: 'email',
value: email,
},
],
},
};

beforeEach(() => {
prismaMock.credential.findUniqueOrThrow.mockResolvedValue(credential);
});

it('should return a sign in form', async () => {
const result = await controller.signIn();
expect(result).toMatch(/<main id="main"/);
expect(result).toMatch(
/<form id="sign-in-form" hx-post="\/auth\/sign-in" hx-trigger="submit" hx-swap="none"/,
);
expect(result).toMatch(/<input type="email"/);
expect(result).toMatch(
/hx-target="#email-input" required placeholder="Enter your email" title="Email"/,
);
expect(result).toMatch(/<input type="password"/);
expect(result).toMatch(
/hx-target="#password-input" required placeholder="Enter your password" title="Password"/,
);
expect(result).toMatch(/<button type="submit"/);
});

it('should return the credential if the user exists and password is correct', async () => {
mockRequest.user = credential;
const result = await controller.signInPost(
{ email, password },
mockRequest,
);
expect(result).toEqual(credential);
});
});
});
Loading

0 comments on commit 08e5bb8

Please sign in to comment.