chore: first commit
This commit is contained in:
217
tests/api-command.spec.ts
Normal file
217
tests/api-command.spec.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import topics from '../topic.json'
|
||||
import crypto from 'crypto'
|
||||
|
||||
// Configuration
|
||||
const API_URL = 'https://api-dev.vegacloud.id'
|
||||
const API_COMMAND_PATH = '/device/v1/command'
|
||||
const API_STATUS_PATH = '/device/v1/status'
|
||||
const API_KEY = 'ak_sdbx:N8Xbvb4OYprJ-Mtw4QO6xw'
|
||||
const SECRET_KEY = 'sk_sdbx:zyJteO7W3szL41fWWB8WZEmKpp8KyJzi-9o9OovbpT4'
|
||||
|
||||
// Convert "01" -> "A", "02" -> "B", etc.
|
||||
function numToAlpha(numStr: string): string {
|
||||
const num = parseInt(numStr, 10)
|
||||
if (isNaN(num)) return numStr
|
||||
return String.fromCharCode(64 + num)
|
||||
}
|
||||
|
||||
// Helper function to sort keys deeply
|
||||
function sortKeysDeep(obj: any): any {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(sortKeysDeep)
|
||||
}
|
||||
if (obj !== null && typeof obj === 'object') {
|
||||
return Object.keys(obj)
|
||||
.sort()
|
||||
.reduce((acc: any, key) => {
|
||||
acc[key] = sortKeysDeep(obj[key])
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// Generate signature for POST request
|
||||
function generateSignatureForPost(body: any): { timestamp: number; nonce: string; signature: string } {
|
||||
const timestamp = Math.floor(Date.now() / 1000)
|
||||
const nonce = crypto.randomUUID()
|
||||
const sortedBody = sortKeysDeep(body)
|
||||
const jsonBody = JSON.stringify({
|
||||
timestamp,
|
||||
nonce,
|
||||
body: JSON.stringify(sortedBody)
|
||||
})
|
||||
|
||||
const signature = crypto
|
||||
.createHmac('sha256', SECRET_KEY)
|
||||
.update(jsonBody)
|
||||
.digest('hex')
|
||||
|
||||
return { timestamp, nonce, signature }
|
||||
}
|
||||
|
||||
// Parse query params from URL manually (matching Postman logic)
|
||||
function parseQueryParamsManual(url: string): Record<string, string> {
|
||||
const resultObject: Record<string, string> = {}
|
||||
const queryStartIndex = url.indexOf('?')
|
||||
|
||||
if (queryStartIndex === -1 || queryStartIndex === url.length - 1) {
|
||||
return resultObject
|
||||
}
|
||||
|
||||
const queryString = url.substring(queryStartIndex + 1)
|
||||
const pairs = queryString.split('&')
|
||||
|
||||
for (let i = 0; i < pairs.length; i++) {
|
||||
const pair = pairs[i]
|
||||
const parts = pair.split('=')
|
||||
|
||||
if (parts.length === 2) {
|
||||
const key = decodeURIComponent(parts[0].replace(/\+/g, ' '))
|
||||
const value = decodeURIComponent(parts[1].replace(/\+/g, ' '))
|
||||
resultObject[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return resultObject
|
||||
}
|
||||
|
||||
// Generate signature for GET request
|
||||
function generateSignatureForGet(url: string): { timestamp: number; nonce: string; signature: string } {
|
||||
const timestamp = Math.floor(Date.now() / 1000)
|
||||
const nonce = crypto.randomUUID()
|
||||
|
||||
const params = parseQueryParamsManual(url)
|
||||
const sortedParams = Object.keys(params)
|
||||
.sort()
|
||||
.reduce((acc: any, key) => {
|
||||
acc[key] = params[key]
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
const jsonBody = JSON.stringify({
|
||||
timestamp,
|
||||
nonce,
|
||||
body: JSON.stringify(sortedParams)
|
||||
})
|
||||
|
||||
const signature = crypto
|
||||
.createHmac('sha256', SECRET_KEY)
|
||||
.update(jsonBody)
|
||||
.digest('hex')
|
||||
|
||||
return { timestamp, nonce, signature }
|
||||
}
|
||||
|
||||
test.describe('Device Command & Status Tests', () => {
|
||||
|
||||
const getPayload = (deviceName: string) => {
|
||||
if (deviceName === 'AC') return 'On'
|
||||
if (deviceName === 'BL') return 'Open'
|
||||
if (deviceName === 'DL') return 'Lock'
|
||||
return null
|
||||
}
|
||||
|
||||
test.describe('1. Command API Tests', () => {
|
||||
for (const item of topics) {
|
||||
const { towerNumber, floorName, unitNumber: originalUnit, deviceName, deviceRoom, deviceType } = item
|
||||
const action = getPayload(deviceName)
|
||||
|
||||
if (action) {
|
||||
const unitNumber = numToAlpha(originalUnit)
|
||||
const payload = {
|
||||
merchantName: 'SAVY',
|
||||
floorName,
|
||||
unitNumber,
|
||||
deviceName,
|
||||
roomName: deviceRoom,
|
||||
deviceType,
|
||||
commandType: 'C',
|
||||
towerNumber,
|
||||
payload: { action }
|
||||
}
|
||||
|
||||
test(`POST Command: ${deviceName} (${deviceType}) - ${floorName}_${unitNumber} - ${deviceRoom}`, async ({ request }) => {
|
||||
const { timestamp, nonce, signature } = generateSignatureForPost(payload)
|
||||
|
||||
console.log(`[Command] Request: ${JSON.stringify(payload, null, 2)}`)
|
||||
|
||||
const response = await request.post(`${API_URL}${API_COMMAND_PATH}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': API_KEY,
|
||||
'X-Timestamp': timestamp.toString(),
|
||||
'X-Nonce': nonce,
|
||||
'X-Signature': signature
|
||||
},
|
||||
data: payload
|
||||
})
|
||||
|
||||
const responseBody = await response.json()
|
||||
console.log(`[Command] Response Status: ${response.status()}`)
|
||||
console.log(`[Command] Response Body: ${JSON.stringify(responseBody, null, 2)}`)
|
||||
console.log('---')
|
||||
|
||||
expect(response.status()).toBe(200)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test.describe('2. Status Check API Tests', () => {
|
||||
for (const item of topics) {
|
||||
const { towerNumber, floorName, unitNumber: originalUnit, deviceName, deviceRoom, deviceType } = item
|
||||
const action = getPayload(deviceName)
|
||||
|
||||
if (action) {
|
||||
const unitNumber = numToAlpha(originalUnit)
|
||||
test(`GET Status: ${deviceName} (${deviceType}) - ${floorName}_${unitNumber} - ${deviceRoom}`, async ({ request }) => {
|
||||
const params = {
|
||||
merchantName: 'SAVY',
|
||||
floorName,
|
||||
unitNumber,
|
||||
deviceName,
|
||||
roomName: deviceRoom,
|
||||
deviceType,
|
||||
commandType: 'S',
|
||||
towerNumber
|
||||
}
|
||||
|
||||
const queryString = new URLSearchParams(params).toString()
|
||||
const url = `${API_URL}${API_STATUS_PATH}?${queryString}`
|
||||
const { timestamp, nonce, signature } = generateSignatureForGet(url)
|
||||
|
||||
console.log(`[Status Check] Request URL: ${url}`)
|
||||
|
||||
const response = await request.get(url, {
|
||||
headers: {
|
||||
'X-API-Key': API_KEY,
|
||||
'X-Timestamp': timestamp.toString(),
|
||||
'X-Nonce': nonce,
|
||||
'X-Signature': signature
|
||||
}
|
||||
})
|
||||
|
||||
const responseBody = await response.json()
|
||||
console.log(`[Status Check] Response Status: ${response.status()}`)
|
||||
console.log(`[Status Check] Response Body: ${JSON.stringify(responseBody, null, 2)}`)
|
||||
console.log('---')
|
||||
|
||||
expect(response.status()).toBe(200)
|
||||
expect(responseBody.data).not.toBeNull()
|
||||
|
||||
// Strict Validation
|
||||
expect(responseBody.data.floorName).toBe(floorName)
|
||||
expect(responseBody.data.unitNumber).toBe(unitNumber)
|
||||
expect(responseBody.data.deviceName).toBe(deviceName)
|
||||
expect(responseBody.data.roomName).toBe(deviceRoom)
|
||||
expect(responseBody.data.deviceType).toBe(deviceType)
|
||||
expect(responseBody.data.code).toBe('S')
|
||||
expect(responseBody.data.towerNumber).toBe(towerNumber)
|
||||
expect(responseBody.data).toHaveProperty('payload')
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user