Skip to content

Commit

Permalink
feat: add TcpCheck construct [sc-22430] (#1012)
Browse files Browse the repository at this point in the history
* feat: added tcp check construct [sc-22430]

* feat: hide misleading url property from the user, use hostname instead

* chore: add unit tests for TcpCheck (copied from ApiCheck)

* chore: add a TcpCheck to e2e tests

* feat: support all current TCP check features

* fix: remove bodyType from TcpCheck, it has no purpose currently

* refactor: update to new API schema

* fix: responseData assertion does not have properties

* Revert "fix: responseData assertion does not have properties"

The property could be a regex, like with API checks and TEXT_BODY, so
expose it after all.

This reverts commit 2c7b8b8.

* chore: fix variable name in tests

* fix(tests): use our site for snapshots instead of an external page that's down

---------

Co-authored-by: ejanusevicius <[email protected]>
  • Loading branch information
sorccu and ejanusevicius authored Jan 29, 2025
1 parent 0173d7b commit 9331762
Show file tree
Hide file tree
Showing 10 changed files with 377 additions and 147 deletions.
2 changes: 2 additions & 0 deletions packages/cli/e2e/__tests__/deploy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ Update and Unchanged:
ApiCheck: api-check-high-freq
HeartbeatCheck: heartbeat-check-1
BrowserCheck: homepage-browser-check
TcpCheck: tcp-check
CheckGroup: my-group-1
Dashboard: dashboard-1
MaintenanceWindow: maintenance-window-1
Expand All @@ -252,6 +253,7 @@ Update and Unchanged:
HeartbeatCheck: heartbeat-check-1
BrowserCheck: homepage-browser-check
BrowserCheck: snapshot-test.test.ts
TcpCheck: tcp-check
CheckGroup: my-group-1
Dashboard: dashboard-1
MaintenanceWindow: maintenance-window-1
Expand Down
13 changes: 13 additions & 0 deletions packages/cli/e2e/__tests__/fixtures/deploy-project/tcp.check.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* eslint-disable no-new */
import { TcpCheck } from 'checkly/constructs'

new TcpCheck('tcp-check', {
name: 'TCP Check',
activated: false,
request: {
hostname: 'api.checklyhq.com',
port: 443,
},
degradedResponseTime: 5000,
maxResponseTime: 20000,
})
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { expect, test } from '@playwright/test'

test.use({ actionTimeout: 10000 })

test('WWW Snapshot Test', async ({ page }) => {
await page.goto('https://info.cern.ch/hypertext/WWW/TheProject.html')
test('Welcome Snapshot Test', async ({ page }) => {
await page.goto('https://welcome.checklyhq.com')
await expect(page).toHaveScreenshot({ maxDiffPixels: 10000 })
console.log(process.env.SECRET_ENV)
})
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
103 changes: 103 additions & 0 deletions packages/cli/src/constructs/__tests__/tcp-check.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { TcpCheck, CheckGroup, TcpRequest } from '../index'
import { Project, Session } from '../project'

const runtimes = {
'2022.10': { name: '2022.10', default: false, stage: 'CURRENT', description: 'Main updates are Playwright 1.28.0, Node.js 16.x and Typescript support. We are also dropping support for Puppeteer', dependencies: { '@playwright/test': '1.28.0', '@opentelemetry/api': '1.0.4', '@opentelemetry/sdk-trace-base': '1.0.1', '@faker-js/faker': '5.5.3', aws4: '1.11.0', axios: '0.27.2', btoa: '1.2.1', chai: '4.3.7', 'chai-string': '1.5.0', 'crypto-js': '4.1.1', expect: '29.3.1', 'form-data': '4.0.0', jsonwebtoken: '8.5.1', lodash: '4.17.21', mocha: '10.1.0', moment: '2.29.2', node: '16.x', otpauth: '9.0.2', playwright: '1.28.0', typescript: '4.8.4', uuid: '9.0.0' } },
}

const request: TcpRequest = {
hostname: 'acme.com',
port: 443,
}

describe('TcpCheck', () => {
it('should not synthesize runtime if not specified even if default runtime is set', () => {
Session.project = new Project('project-id', {
name: 'Test Project',
repoUrl: 'https://github.com/checkly/checkly-cli',
})
Session.availableRuntimes = runtimes
Session.defaultRuntimeId = '2022.02'
const check = new TcpCheck('test-check', {
name: 'Test Check',
request,
})
const payload = check.synthesize()
expect(payload.runtimeId).toBeUndefined()
delete Session.defaultRuntimeId
})

it('should synthesize runtime if specified', () => {
Session.project = new Project('project-id', {
name: 'Test Project',
repoUrl: 'https://github.com/checkly/checkly-cli',
})
Session.availableRuntimes = runtimes
Session.defaultRuntimeId = '2022.02'
const check = new TcpCheck('test-check', {
name: 'Test Check',
runtimeId: '2022.02',
request,
})
const payload = check.synthesize()
expect(payload.runtimeId).toEqual('2022.02')
delete Session.defaultRuntimeId
})

it('should apply default check settings', () => {
Session.project = new Project('project-id', {
name: 'Test Project',
repoUrl: 'https://github.com/checkly/checkly-cli',
})
Session.checkDefaults = { tags: ['default tags'] }
const check = new TcpCheck('test-check', {
name: 'Test Check',
request,
})
delete Session.checkDefaults
expect(check).toMatchObject({ tags: ['default tags'] })
})

it('should overwrite default check settings with check-specific config', () => {
Session.project = new Project('project-id', {
name: 'Test Project',
repoUrl: 'https://github.com/checkly/checkly-cli',
})
Session.checkDefaults = { tags: ['default tags'] }
const check = new TcpCheck('test-check', {
name: 'Test Check',
tags: ['test check'],
request,
})
delete Session.checkDefaults
expect(check).toMatchObject({ tags: ['test check'] })
})

it('should support setting groups with `groupId`', () => {
Session.project = new Project('project-id', {
name: 'Test Project',
repoUrl: 'https://github.com/checkly/checkly-cli',
})
const group = new CheckGroup('main-group', { name: 'Main Group', locations: [] })
const check = new TcpCheck('main-check', {
name: 'Main Check',
request,
groupId: group.ref(),
})
expect(check.synthesize()).toMatchObject({ groupId: { ref: 'main-group' } })
})

it('should support setting groups with `group`', () => {
Session.project = new Project('project-id', {
name: 'Test Project',
repoUrl: 'https://github.com/checkly/checkly-cli',
})
const group = new CheckGroup('main-group', { name: 'Main Group', locations: [] })
const check = new TcpCheck('main-check', {
name: 'Main Check',
request,
group,
})
expect(check.synthesize()).toMatchObject({ groupId: { ref: 'main-group' } })
})
})
159 changes: 14 additions & 145 deletions packages/cli/src/constructs/api-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,172 +6,41 @@ import { QueryParam } from './query-param'
import { pathToPosix } from '../services/util'
import { printDeprecationWarning } from '../reporters/util'
import { Content, Entrypoint } from './construct'
import { Assertion as CoreAssertion, NumericAssertionBuilder, GeneralAssertionBuilder } from './internal/assertion'

// eslint-disable-next-line no-restricted-syntax
enum AssertionSource {
STATUS_CODE = 'STATUS_CODE',
JSON_BODY = 'JSON_BODY',
HEADERS = 'HEADERS',
TEXT_BODY = 'TEXT_BODY',
RESPONSE_TIME = 'RESPONSE_TIME',
}

// eslint-disable-next-line no-restricted-syntax
enum AssertionComparison {
EQUALS = 'EQUALS',
NOT_EQUALS = 'NOT_EQUALS',
HAS_KEY = 'HAS_KEY',
NOT_HAS_KEY = 'NOT_HAS_KEY',
HAS_VALUE = 'HAS_VALUE',
NOT_HAS_VALUE = 'NOT_HAS_VALUE',
IS_EMPTY = 'IS_EMPTY',
NOT_EMPTY = 'NOT_EMPTY',
GREATER_THAN = 'GREATER_THAN',
LESS_THAN = 'LESS_THAN',
CONTAINS = 'CONTAINS',
NOT_CONTAINS = 'NOT_CONTAINS',
IS_NULL = 'IS_NULL',
NOT_NULL = 'NOT_NULL',
}
type AssertionSource =
| 'STATUS_CODE'
| 'JSON_BODY'
| 'HEADERS'
| 'TEXT_BODY'
| 'RESPONSE_TIME'

export interface Assertion {
source: string,
property: string,
comparison: string,
target: string,
regex: string|null,
}
export type Assertion = CoreAssertion<AssertionSource>

export class AssertionBuilder {
static statusCode () {
return new NumericAssertionBuilder(AssertionSource.STATUS_CODE)
return new NumericAssertionBuilder<AssertionSource>('STATUS_CODE')
}

static jsonBody (property?: string) {
return new GeneralAssertionBuilder(AssertionSource.JSON_BODY, property)
return new GeneralAssertionBuilder<AssertionSource>('JSON_BODY', property)
}

static headers (property?: string, regex?: string) {
return new GeneralAssertionBuilder(AssertionSource.HEADERS, property, regex)
return new GeneralAssertionBuilder<AssertionSource>('HEADERS', property, regex)
}

static textBody (property?: string) {
return new GeneralAssertionBuilder(AssertionSource.TEXT_BODY, property)
return new GeneralAssertionBuilder<AssertionSource>('TEXT_BODY', property)
}

/** @deprecated Use responseTime() instead */
static responseTme () {
return new NumericAssertionBuilder(AssertionSource.RESPONSE_TIME)
return new NumericAssertionBuilder<AssertionSource>('RESPONSE_TIME')
}

static responseTime () {
return new NumericAssertionBuilder(AssertionSource.RESPONSE_TIME)
}
}

class NumericAssertionBuilder {
source: AssertionSource
constructor (source: AssertionSource) {
this.source = source
}

equals (target: number): Assertion {
return this._toAssertion(AssertionComparison.EQUALS, target)
}

notEquals (target: number): Assertion {
return this._toAssertion(AssertionComparison.NOT_EQUALS, target)
}

lessThan (target: number): Assertion {
return this._toAssertion(AssertionComparison.LESS_THAN, target)
}

greaterThan (target: number): Assertion {
return this._toAssertion(AssertionComparison.GREATER_THAN, target)
}

/** @private */
private _toAssertion (comparison: AssertionComparison, target: number): Assertion {
return { source: this.source, comparison, property: '', target: target.toString(), regex: null }
}
}

class GeneralAssertionBuilder {
source: AssertionSource
property?: string
regex?: string
constructor (source: AssertionSource, property?: string, regex?: string) {
this.source = source
this.property = property
this.regex = regex
}

equals (target: string|number|boolean): Assertion {
return this._toAssertion(AssertionComparison.EQUALS, target)
}

notEquals (target: string|number|boolean): Assertion {
return this._toAssertion(AssertionComparison.NOT_EQUALS, target)
}

hasKey (target: string): Assertion {
return this._toAssertion(AssertionComparison.HAS_KEY, target)
}

notHasKey (target: string): Assertion {
return this._toAssertion(AssertionComparison.NOT_HAS_KEY, target)
}

hasValue (target: string|number|boolean): Assertion {
return this._toAssertion(AssertionComparison.HAS_VALUE, target)
}

notHasValue (target: string|number|boolean): Assertion {
return this._toAssertion(AssertionComparison.NOT_HAS_VALUE, target)
}

isEmpty () {
return this._toAssertion(AssertionComparison.IS_EMPTY)
}

notEmpty () {
return this._toAssertion(AssertionComparison.NOT_EMPTY)
}

lessThan (target: string|number|boolean): Assertion {
return this._toAssertion(AssertionComparison.LESS_THAN, target)
}

greaterThan (target: string|number|boolean): Assertion {
return this._toAssertion(AssertionComparison.GREATER_THAN, target)
}

contains (target: string): Assertion {
return this._toAssertion(AssertionComparison.CONTAINS, target)
}

notContains (target: string): Assertion {
return this._toAssertion(AssertionComparison.NOT_CONTAINS, target)
}

isNull () {
return this._toAssertion(AssertionComparison.IS_NULL)
}

isNotNull () {
return this._toAssertion(AssertionComparison.NOT_NULL)
}

/** @private */
private _toAssertion (comparison: AssertionComparison, target?: string|number|boolean): Assertion {
return {
source: this.source,
comparison,
property: this.property ?? '',
target: target?.toString() ?? '',
regex: this.regex ?? null,
}
return new NumericAssertionBuilder<AssertionSource>('RESPONSE_TIME')
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/constructs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ export * from './phone-call-alert-channel'
export * from './retry-strategy'
export * from './multi-step-check'
export * from './alert-escalation-policy'
export * from './tcp-check'
Loading

0 comments on commit 9331762

Please sign in to comment.