Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/config/best-practice-reference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import ProjectFramework from "../entities/submission-project/project-framework";

export interface BestPracticeReference {
name: string
url: string
description: string
}

const hapiReference: BestPracticeReference = {
name: 'Hapi Framework',
url: 'https://hapi.dev/tutorials/?lang=en_US',
description: 'Kamu bisa eksplor tentang apa saja yang bisa digunakan pada framework Hapi.'
}

const expressReference: BestPracticeReference = {
name: 'Express Framework',
url: 'https://expressjs.com/en/guide/routing.html',
description: 'Kamu bisa eksplor tentang apa saja yang bisa digunakan pada framework Express, mulai dari routing sampai middleware.'
}

const joiReference: BestPracticeReference = {
name: 'Joi',
url: 'https://joi.dev/',
description: 'Jika kamu ingin membuat validasi data dengan mudah kamu juga bisa menggunakan library Joi.'
}

const expressValidatorReference: BestPracticeReference = {
name: 'express-validator',
url: 'https://express-validator.github.io/docs/',
description: 'Jika kamu ingin membuat validasi data dengan mudah kamu juga bisa menggunakan library express-validator.'
}

const databaseReferences: BestPracticeReference[] = [
{
name: 'Postgres',
url: 'https://www.postgresql.org/docs/current/index.html',
description: 'Agar aplikasi yang kamu buat datanya bisa bertahan ketika server direstart, kamu bisa mempelajari PostgreSQL sebagai penyimpanan data.'
},
{
name: 'node-postgres',
url: 'https://node-postgres.com/',
description: 'Untuk menghubungkan aplikasi nodejs dengan postgresql kamu bisa menggunakan library node-postgres.'
}
]

const bestPracticeReference: Record<ProjectFramework, BestPracticeReference[]> = {
[ProjectFramework.Hapi]: [hapiReference, joiReference, ...databaseReferences],
[ProjectFramework.Express]: [expressReference, expressValidatorReference, ...databaseReferences],
[ProjectFramework.Unknown]: [hapiReference, expressReference, joiReference, ...databaseReferences]
}

export default bestPracticeReference
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import EslintCheckResult from "../../../service/eslint-checker/eslint-check-resu
import SubmissionRatingFactory from "../../../factories/submission-rating/submission-rating-factory";
import SubmissionCriteriaCheck from "../submission-criteria-check/submission-criteria-check";
import getSubmissionRequirement from "../../../config/submission-requirement";
import ProjectFramework from "../../submission-project/project-framework";

describe('course submission acception test', () => {
it('should accept submission properly', function () {
Expand Down Expand Up @@ -115,4 +116,66 @@ describe('course submission acception test', () => {
expect(courseSubmissionAcception.rating).toStrictEqual(4)
expect(courseSubmissionAcception.messages).toContain('...')
});

describe('best practice reference on advice message', () => {
const acceptFlawlessSubmission = (projectFramework?: ProjectFramework) => {
const submissionRatingGenerator = <SubmissionRatingFactory>{
get rating(): number {
return 5
},
get eslintCheckResult(): EslintCheckResult {
return <EslintCheckResult>{
get isSuccess(): boolean {
return true
},
}
}
}

const courseSubmissionAcception = new CourseSubmissionAcception(
<SubmissionCriteriaCheck>{}, submissionRatingGenerator, projectFramework
)
courseSubmissionAcception.accept()

return courseSubmissionAcception.messages
}

it('should only suggest hapi documentation when project use hapi', function () {
const messages = acceptFlawlessSubmission(ProjectFramework.Hapi)

expect(messages).toContain('<strong>Hapi Framework</strong>')
expect(messages).toContain('<strong>Joi</strong>')
expect(messages).not.toContain('<strong>Express Framework</strong>')
expect(messages).not.toContain('<strong>express-validator</strong>')
});

it('should only suggest express documentation when project use express', function () {
const messages = acceptFlawlessSubmission(ProjectFramework.Express)

expect(messages).toContain('<strong>Express Framework</strong>')
expect(messages).toContain('<strong>express-validator</strong>')
expect(messages).not.toContain('<strong>Hapi Framework</strong>')
expect(messages).not.toContain('<strong>Joi</strong>')
});

it('should suggest both framework documentation when framework is unknown', function () {
const messages = acceptFlawlessSubmission(ProjectFramework.Unknown)

expect(messages).toContain('<strong>Hapi Framework</strong>')
expect(messages).toContain('<strong>Express Framework</strong>')
});

it('should fall back to unknown framework when framework is not given', function () {
expect(acceptFlawlessSubmission()).toStrictEqual(acceptFlawlessSubmission(ProjectFramework.Unknown))
});

it('should always suggest database documentation', function () {
[ProjectFramework.Hapi, ProjectFramework.Express, ProjectFramework.Unknown].forEach(projectFramework => {
const messages = acceptFlawlessSubmission(projectFramework)

expect(messages).toContain('<strong>Postgres</strong>')
expect(messages).toContain('<strong>node-postgres</strong>')
})
});
});
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import exceptionToReviewMessage from "../../../exception/exception-to-review-mes
import SubmissionRatingFactory from "../../../factories/submission-rating/submission-rating-factory";
import SubmissionCriteriaCheck from "../submission-criteria-check/submission-criteria-check";
import {SubmissionRequirement} from "../../../config/submission-requirement";
import ProjectFramework from "../../submission-project/project-framework";
import bestPracticeReference from "../../../config/best-practice-reference";


class CourseSubmissionAcception {
Expand All @@ -10,11 +12,17 @@ class CourseSubmissionAcception {
private submissionRatingGenerator: SubmissionRatingFactory;
private readonly _reviewChecklistResults: SubmissionRequirement;
private submissionCriteriaCheck: SubmissionCriteriaCheck;
private readonly projectFramework: ProjectFramework;

constructor(submissionCriteriaCheck: SubmissionCriteriaCheck, submissionRatingGenerator: SubmissionRatingFactory) {
constructor(
submissionCriteriaCheck: SubmissionCriteriaCheck,
submissionRatingGenerator: SubmissionRatingFactory,
projectFramework: ProjectFramework = ProjectFramework.Unknown
) {
this.submissionRatingGenerator = submissionRatingGenerator;
this._reviewChecklistResults = submissionCriteriaCheck.reviewChecklistResult;
this.submissionCriteriaCheck = submissionCriteriaCheck
this.projectFramework = projectFramework
}

accept() {
Expand All @@ -28,7 +36,18 @@ class CourseSubmissionAcception {
if (messageFromEslint || messageFromOptionalTest) {
return messageFromEslint + messageFromOptionalTest
}
return '<li>Untuk mengetahui best practices yang ada, terutama penggunaan framework atau tools, cara terbaiknya adalah dengan bereksplorasi kepada dokumentasi resmi yang diberikan. Semakin kamu mengenal frameworknya tentu semakin paham best practice penggunaannya. Silakan eksplorasi dokumentasi beberapa stack framework dan tools yang dapat digunakan pada proyekmu.<ul><li><strong>Hapi Framework</strong>:&nbsp;<a data-mce-href="https://hapi.dev/tutorials/?lang=en_US" data-target-href="https://hapi.dev/tutorials/?lang=en_US" href="https://hapi.dev/tutorials/?lang=en_US" rel="noreferrer nofollow noopener">https://hapi.dev/tutorials/?lang=en_US</a><br>Kamu bisa eksplor tentang apa saja yang bisa digunakan pada framework Hapi.</li><li><strong>Joi</strong>:&nbsp;<a data-mce-href="https://joi.dev/" data-target-href="https://joi.dev/" href="https://joi.dev/" rel="noreferrer nofollow noopener">https://joi.dev/</a><br>Jika kamu ingin membuat validasi data dengan mudah kamu juga bisa menggunakan library Joi.</li><li><strong>Postgres</strong>:&nbsp;<a data-mce-href="https://www.postgresql.org/docs/current/index.html" data-target-href="https://www.postgresql.org/docs/current/index.html" href="https://www.postgresql.org/docs/current/index.html" rel="noreferrer nofollow noopener">https://www.postgresql.org/docs/current/index.html</a><br>Agar aplikasi yang kamu buat datanya bisa bertahan ketika server direstart, kamu bisa mempelajari postrgresql sebagai penyimpanan data.</li><li><strong>node-postgres</strong>:&nbsp;<a data-mce-href="https://node-postgres.com/" data-target-href="https://node-postgres.com/" href="https://node-postgres.com/" rel="noreferrer nofollow noopener">https://node-postgres.com/</a><br>Untuk menghubungkan aplikasi nodejs dengan postgresql kamu bisa menggunakan library node-postgres.</li></ul></li>'
return this.getMessageFromBestPracticeReference()
}

private getMessageFromBestPracticeReference(): string {
const references = bestPracticeReference[this.projectFramework]
.map(reference => {
const link = `<a data-mce-href="${reference.url}" data-target-href="${reference.url}" href="${reference.url}" rel="noreferrer nofollow noopener">${reference.url}</a>`
return `<li><strong>${reference.name}</strong>:&nbsp;${link}<br>${reference.description}</li>`
})
.join('')

return `<li>Untuk mengetahui best practices yang ada, terutama penggunaan framework atau tools, cara terbaiknya adalah dengan bereksplorasi kepada dokumentasi resmi yang diberikan. Semakin kamu mengenal frameworknya tentu semakin paham best practice penggunaannya. Silakan eksplorasi dokumentasi beberapa stack framework dan tools yang dapat digunakan pada proyekmu.<ul>${references}</ul></li>`
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,24 @@ import EslintCheckResult from "../../../service/eslint-checker/eslint-check-resu
import SubmissionCriteriaCheck from "../submission-criteria-check/submission-criteria-check";
import ReviewResult, {ReviewResultStatus} from "./review-result";
import SubmissionRatingFactory from "../../../factories/submission-rating/submission-rating-factory";
import ProjectFramework from "../../submission-project/project-framework";

class CourseSubmissionReview {
private readonly submissionCriteriaCheck: SubmissionCriteriaCheck;
private readonly rejectException?: SubmissionErrorException;
private readonly eslintCheckResult?: EslintCheckResult;
private readonly projectFramework: ProjectFramework;

constructor(
submissionCriteriaCheck: SubmissionCriteriaCheck,
eslintCheckResult?: EslintCheckResult,
rejectException?: SubmissionErrorException
rejectException?: SubmissionErrorException,
projectFramework: ProjectFramework = ProjectFramework.Unknown
) {
this.rejectException = rejectException;
this.submissionCriteriaCheck = submissionCriteriaCheck;
this.eslintCheckResult = eslintCheckResult;
this.projectFramework = projectFramework;
}


Expand All @@ -32,7 +36,7 @@ class CourseSubmissionReview {

private generateApproval(): ReviewResult {
const submissionRatingGenerator = new SubmissionRatingFactory(this.submissionCriteriaCheck.failurePostmanTest, this.eslintCheckResult)
const courseSubmissionAcception = new CourseSubmissionAcception(this.submissionCriteriaCheck, submissionRatingGenerator)
const courseSubmissionAcception = new CourseSubmissionAcception(this.submissionCriteriaCheck, submissionRatingGenerator, this.projectFramework)
courseSubmissionAcception.accept()

return <ReviewResult>{
Expand Down
4 changes: 3 additions & 1 deletion src/entities/submission-project/package-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ interface PackageJson {
scripts?: object
dependencies?: Dependencies
devDependencies?: Dependencies
eslintConfig?: object
}

interface Dependencies{
eslint?: string
[packageName: string]: string | undefined
}

export default PackageJson
export default PackageJson
7 changes: 7 additions & 0 deletions src/entities/submission-project/project-framework.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
enum ProjectFramework {
Hapi = 'HAPI',
Express = 'EXPRESS',
Unknown = 'UNKNOWN'
}

export default ProjectFramework
6 changes: 4 additions & 2 deletions src/entities/submission-project/submission-project.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import PackageJson from "./package-json";
import ProjectFramework from "./project-framework";

export default interface SubmissionProject {
packageJsonContent: PackageJson,
packageJsonPath: string,
runnerCommand: string
}
runnerCommand: string,
framework: ProjectFramework
}
1 change: 1 addition & 0 deletions src/exception/exception-to-review-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const exceptionToReviewMessage = {
'RUNNER_SCRIPT_NOT_FOUND': 'sepertinya kamu lupa memasukkan command untuk menjalankan project ke dalam properti "script" pada file package.json. Silakan ditambahkan terlebih dahulu yaa.',
'CANNOT_PARSE_PACKAGE_JSON': 'sepertinya file package.json yang kamu buat belum bisa terformat dengan baik. Silakan diperbaiki yaa.',
'ESLINT_NOT_INSTALLED': 'Aplikasi yang kamu buat sudah berjalan dengan baik. Tetapi kode yang dituliskan masih ada yang bisa ditingkatkan. Sebaiknya gunakan salah satu style guide yang ada pada eslint agar kode yang kamu buat bisa semakin konsisten. Tingkatkan aplikasinya untuk latihan yaa. Semangattt!!',
'ESLINT_CONFIG_NOT_FOUND': 'Aplikasi yang kamu buat sudah berjalan dengan baik. Kamu juga sudah menambahkan eslint pada file package.json, tetapi belum ada file konfigurasi eslint pada project yang kamu kirimkan sehingga eslint belum benar-benar berjalan. Silakan tambahkan file konfigurasinya, contohnya <b>.eslintrc.json</b> atau <b>eslint.config.js</b>, lalu gunakan salah satu style guide yang ada agar kode yang kamu buat bisa semakin konsisten. Tingkatkan aplikasinya untuk latihan yaa. Semangattt!!',
'ESLINT_ERROR' : 'Masih terdapat error ketika eslint dijalankan dengan perintah npx eslint ./src. Sebaiknya diperbaiki yaa, pastikan tidak ada warning atau error pada kode yang kamu buat.',
'SEVER_FAILED_TO_START': '',
'PORT_NOT_MEET_REQUIREMENT': 'port yang kamu gunakan untuk menjalankan aplikasi masih belum sesuai dengan kriteria submission, silakan dicek kembali kriterianya yaa.',
Expand Down
23 changes: 22 additions & 1 deletion src/factories/submission-project/submission-project-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import PackageJson from "../../entities/submission-project/package-json";
import SubmissionProject from "../../entities/submission-project/submission-project";
import {SubmissionRequirement} from "../../config/submission-requirement";
import domainEvent from "../../common/domain-event";
import ProjectFramework from "../../entities/submission-project/project-framework";


export default class SubmissionProjectFactory {
Expand All @@ -25,10 +26,30 @@ export default class SubmissionProjectFactory {
return {
packageJsonContent: this.packageJsonContent,
packageJsonPath: projectPath,
runnerCommand
runnerCommand,
framework: this.getFramework()
}
}

private getFramework(): ProjectFramework {
const dependencies = {
...this.packageJsonContent.dependencies,
...this.packageJsonContent.devDependencies
}

const detectedFrameworks = [
{framework: ProjectFramework.Hapi, packages: ['@hapi/hapi', 'hapi']},
{framework: ProjectFramework.Express, packages: ['express']}
].filter(candidate => candidate.packages.some(packageName => packageName in dependencies))

// a project using both frameworks is ambiguous, so it gets the generic advice
if (detectedFrameworks.length !== 1) {
return ProjectFramework.Unknown
}

return detectedFrameworks[0].framework
}

private validate(projectPath: string) {
if (projectPath === null) {
throw new ProjectErrorException('PATH_NOT_CONTAIN_PACKAGE_JSON')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as fs from "fs";
import ProjectErrorException from "../../exception/project-error-exception";
import SubmissionProjectFactory from "./submission-project-factory";
import getSubmissionRequirement from "../../config/submission-requirement";
import ProjectFramework from "../../entities/submission-project/project-framework";

/**
* @review need to remove mock
Expand Down Expand Up @@ -87,4 +88,43 @@ describe('create submission project test', () => {
expect(submissionProject.packageJsonPath).toStrictEqual('/home/app/1234')
expect(submissionRequirement.project_have_correct_runner_script.status).toBeTruthy()
});

describe('framework detection', () => {
const createWithDependencies = (dependencies: string) => {
mockFS.readFileSync.mockReturnValue(`{ "scripts": { "start": "node src/index.js" }, ${dependencies} }`)
mockFS.existsSync.mockReturnValue(false)

return submissionProjectFactory.create(submissionRequirement, '/home/app/1234')
}

it('should detect hapi framework', function () {
expect(createWithDependencies('"dependencies": { "@hapi/hapi": "^21.4.3" }').framework)
.toStrictEqual(ProjectFramework.Hapi)
});

it('should detect hapi framework from its legacy package name', function () {
expect(createWithDependencies('"dependencies": { "hapi": "^18.1.0" }').framework)
.toStrictEqual(ProjectFramework.Hapi)
});

it('should detect express framework', function () {
expect(createWithDependencies('"dependencies": { "express": "^5.1.0" }').framework)
.toStrictEqual(ProjectFramework.Express)
});

it('should detect framework listed on dev dependencies', function () {
expect(createWithDependencies('"devDependencies": { "express": "^5.1.0" }').framework)
.toStrictEqual(ProjectFramework.Express)
});

it('should not detect any framework when project use both of them', function () {
expect(createWithDependencies('"dependencies": { "express": "^5.1.0", "@hapi/hapi": "^21.4.3" }').framework)
.toStrictEqual(ProjectFramework.Unknown)
});

it('should not detect any framework when project has no dependencies', function () {
expect(createWithDependencies('"dependencies": { "nanoid": "^5.1.6" }').framework)
.toStrictEqual(ProjectFramework.Unknown)
});
});
})
Loading
Loading