Files
savy-test-playwright/tests/api-command.spec.ts
2026-01-19 08:54:54 +07:00

218 lines
6.7 KiB
TypeScript

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')
})
}
}
})
})