218 lines
6.7 KiB
TypeScript
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')
|
|
})
|
|
}
|
|
}
|
|
})
|
|
})
|