Files
pos-system/apps/app-client-base-swift/AppClientBaseSwift/APITestView.swift

307 lines
11 KiB
Swift

//
// APITestView.swift
// AppClientBaseSwift
//
// Debug view for testing API responses
// View debug đ test API responses
//
import SwiftUI
// MARK: - API Test View
// View test API
/// Debug view to test API endpoints
/// View debug đ test các API endpoints
struct APITestView: View {
@State private var testResult: String = "Chưa có kết quả"
@State private var isLoading: Bool = false
@State private var rawJSON: String = ""
@State private var accessToken: String = ""
var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 20) {
// Access Token Input
VStack(alignment: .leading, spacing: 8) {
Text("Access Token")
.font(.headline)
TextEditor(text: $accessToken)
.frame(height: 100)
.padding(8)
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
.font(.system(.caption, design: .monospaced))
Text("Paste access token từ Keychain hoặc sau khi login thành công")
.font(.caption)
.foregroundColor(.secondary)
}
.padding()
// Test Buttons
VStack(spacing: 12) {
Button("Test /users/me Endpoint") {
Task {
await testUsersMeEndpoint()
}
}
.buttonStyle(TestButtonStyle(color: .blue))
Button("Test với Mock Token") {
Task {
await testWithMockToken()
}
}
.buttonStyle(TestButtonStyle(color: .orange))
Button("Clear Results") {
testResult = "Đã xóa kết quả"
rawJSON = ""
}
.buttonStyle(TestButtonStyle(color: .red))
}
.padding()
.disabled(isLoading)
// Loading Indicator
if isLoading {
ProgressView("Đang test API...")
.padding()
}
// Test Result
VStack(alignment: .leading, spacing: 8) {
Text("Kết quả:")
.font(.headline)
ScrollView {
Text(testResult)
.font(.system(.caption, design: .monospaced))
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
}
.frame(maxHeight: 200)
}
.padding()
// Raw JSON
if !rawJSON.isEmpty {
VStack(alignment: .leading, spacing: 8) {
Text("Raw JSON Response:")
.font(.headline)
ScrollView {
Text(rawJSON)
.font(.system(.caption, design: .monospaced))
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(Color.green.opacity(0.1))
.cornerRadius(8)
}
.frame(maxHeight: 300)
Button("Copy JSON") {
UIPasteboard.general.string = rawJSON
testResult = "✅ Đã copy JSON vào clipboard!"
}
.buttonStyle(TestButtonStyle(color: .green))
}
.padding()
}
// Instructions
VStack(alignment: .leading, spacing: 8) {
Text("Hướng dẫn:")
.font(.headline)
VStack(alignment: .leading, spacing: 4) {
Text("1. Login thành công trước")
Text("2. Access token sẽ tự động được lấy từ Keychain")
Text("3. Nhấn 'Test /users/me Endpoint'")
Text("4. Xem Raw JSON để biết server trả về gì")
Text("5. So sánh với User model trong code")
}
.font(.caption)
.foregroundColor(.secondary)
}
.padding()
.background(Color.blue.opacity(0.1))
.cornerRadius(8)
.padding()
}
}
.navigationTitle("API Debugger")
.navigationBarTitleDisplayMode(.inline)
}
.onAppear {
// Load current access token
if let token = AuthManager.shared.accessToken {
accessToken = token
}
}
}
// MARK: - Test Methods
/// Test /users/me endpoint
private func testUsersMeEndpoint() async {
isLoading = true
testResult = "🔄 Đang gọi API..."
do {
// Build URL
guard let url = URL(string: APIConfig.baseURL + APIConfig.apiVersion + "/users/me") else {
testResult = "❌ Invalid URL"
isLoading = false
return
}
// Create request
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
// Use token from input or Keychain
let token = accessToken.isEmpty ? AuthManager.shared.accessToken : accessToken
if let token = token, !token.isEmpty {
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}
testResult = """
📤 Request Details:
URL: \(url.absoluteString)
Method: GET
Token: \(token?.prefix(20) ?? "None")...
⏳ Waiting for response...
"""
// Perform request
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
testResult = "❌ Invalid response"
isLoading = false
return
}
// Get raw JSON
if let jsonString = String(data: data, encoding: .utf8) {
rawJSON = jsonString.prettyPrintedJSON ?? jsonString
}
// Parse result
testResult = """
📥 Response Details:
Status Code: \(httpResponse.statusCode)
"""
if httpResponse.statusCode == 200 {
testResult += "✅ Status: SUCCESS\n\n"
// Try to decode as User
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
do {
let user = try decoder.decode(User.self, from: data)
testResult += """
✅ Decoded User Successfully:
ID: \(user.id)
Email: \(user.email)
Name: \(user.name)
Avatar: \(user.avatarUrl ?? "nil")
Phone: \(user.phoneNumber ?? "nil")
Email Verified: \(user.isEmailVerified)
"""
} catch let decodingError {
testResult += """
❌ Decoding Failed:
\(decodingError.localizedDescription)
💡 Check Raw JSON below để xem field nào bị thiếu hoặc sai tên
"""
}
} else if httpResponse.statusCode == 401 {
testResult += "❌ Status: UNAUTHORIZED (Token invalid or expired)\n"
} else {
testResult += "❌ Status: ERROR\n"
if let errorMessage = String(data: data, encoding: .utf8) {
testResult += "Error: \(errorMessage)\n"
}
}
} catch {
testResult = """
❌ Request Failed:
\(error.localizedDescription)
"""
rawJSON = ""
}
isLoading = false
}
/// Test with mock token
private func testWithMockToken() async {
testResult = """
⚠️ Testing với mock token...
Lưu ý: Request này sẽ fail với 401 Unauthorized
Mục đích: Kiểm tra server response format khi unauthorized
"""
accessToken = "mock_token_for_testing"
await testUsersMeEndpoint()
}
}
// MARK: - Test Button Style
struct TestButtonStyle: ButtonStyle {
let color: Color
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(
color.opacity(configuration.isPressed ? 0.7 : 1.0)
)
.cornerRadius(10)
}
}
// MARK: - String Extension for Pretty JSON
extension String {
var prettyPrintedJSON: String? {
guard let data = self.data(using: .utf8),
let jsonObject = try? JSONSerialization.jsonObject(with: data),
let prettyData = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted),
let prettyString = String(data: prettyData, encoding: .utf8) else {
return nil
}
return prettyString
}
}
// MARK: - Preview
#Preview {
APITestView()
}