ZaStoGram_desktop/Telegram/SourceFiles/gram/tests/gram_nft_tests.cpp
John Preston 17b40609b6 Load collectible artwork through upload.getWebFile
Task: 2026/09/29/load-collectible-media-only-through-upload-get-web-file
2026-10-08 22:24:47 +04:00

774 lines
25 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "gram/tests/gram_tests.h"
#include "gram/api/gram_api_nft.h"
#include <vector>
namespace Gram::Tests {
namespace {
const auto kOwner1Raw = u"0:9DA971AF38D2F03ABDF308D5F91636A97E5A2B07A66C39D71D7CBAE3B032EDDC"_q;
} // namespace
std::vector<Check> NftChecks() {
return {
{ u"nft_builder_item_by_address"_q, [] {
const auto raw = u"0:ca0cfd519f763102b5bec9d9e3af4359"
u"2ea362fb773fa319ea09c4f162c171e0"_q;
return CheckRequest(
NftItemByAddressRequest(raw),
false,
u"/api/v3/nft/items"_q,
u"address=0%3ACA0CFD519F763102B5BEC9D9E3AF4359"
u"2EA362FB773FA319EA09C4F162C171E0"_q,
QByteArray());
} },
{ u"nft_items_owner1_fixture"_q, [] {
const auto name = u"api-nft-items-owner1.json"_q;
const auto bytes = ReadFixture(name);
if (bytes.isEmpty()) {
return u"fixture read failed: "_q + name;
}
const auto page = ParseNftItems(bytes, 100);
if (!page) {
return u"parse failed: "_q + name;
} else if (int(page->list.size()) != 70) {
return u"count: got "_q
+ QString::number(page->list.size())
+ u", expected 70"_q;
} else if (page->hasNext) {
return u"limit 100: expected hasNext false"_q;
}
const auto exact = ParseNftItems(bytes, 70);
if (!exact) {
return u"parse failed: "_q + name;
} else if (!exact->hasNext) {
return u"limit 70: expected hasNext true"_q;
}
const auto address = u"0:8A81E2A362D3E09E35AA8FB32EE175E4"
u"690AFD746B71C1B5503F130191E88FA7"_q;
const auto collection = u"0:0C8F3FCC4ABD589206A2CDF1469E3709"
u"2C1ADAD272FBE7DB97104569C16F0FF2"_q;
const auto uri = u"http://192.236.162.114/nft/1.json"_q;
const auto &item = page->list.front();
if (item.address.toUpper() != address) {
return u"address: got "_q
+ item.address.toUpper()
+ u", expected "_q
+ address;
} else if (item.index != u"1076"_q) {
return u"index: got "_q + item.index + u", expected 1076"_q;
} else if (item.collection.toUpper() != collection) {
return u"collection: got "_q
+ item.collection.toUpper()
+ u", expected "_q
+ collection;
} else if (item.contentUri != uri) {
return u"contentUri: got "_q
+ item.contentUri
+ u", expected "_q
+ uri;
} else if (item.onSale) {
return u"onSale: got true, expected false"_q;
} else if (item.kind != NftKind::Generic) {
return u"kind: got "_q
+ QString::number(int(item.kind))
+ u", expected Generic"_q;
} else if (!item.key.isEmpty()) {
return u"key: got "_q + item.key + u", expected none"_q;
} else if (item.realOwner.toUpper() != kOwner1Raw) {
return u"realOwner: got "_q
+ item.realOwner.toUpper()
+ u", expected "_q
+ kOwner1Raw;
}
const auto pagedName = u"api-nft-items-owner1-paged.json"_q;
const auto pagedBytes = ReadFixture(pagedName);
if (pagedBytes.isEmpty()) {
return u"fixture read failed: "_q + pagedName;
}
const auto paged = ParseNftItems(pagedBytes, 50);
if (!paged) {
return u"parse failed: "_q + pagedName;
} else if (int(paged->list.size()) != 50) {
return u"paged count: got "_q
+ QString::number(paged->list.size())
+ u", expected 50"_q;
} else if (!paged->hasNext) {
return u"paged: expected hasNext true"_q;
}
return QString();
} },
{ u"nft_items_index_precision"_q, [] {
const auto name = u"api-nft-items-address-gift.json"_q;
const auto bytes = ReadFixture(name);
if (bytes.isEmpty()) {
return u"fixture read failed: "_q + name;
}
const auto page = ParseNftItems(bytes, 100);
if (!page) {
return u"parse failed: "_q + name;
} else if (int(page->list.size()) != 1) {
return u"gift count: got "_q
+ QString::number(page->list.size())
+ u", expected 1"_q;
}
const auto expected = u"5800280859629342981385244539048694670709"
u"8687748635984871519305117335532662285"_q;
const auto &index = page->list.front().index;
if (index != expected) {
return u"gift index: got "_q
+ index
+ u", expected "_q
+ expected;
} else if (int(index.size()) != 77) {
return u"gift index size: got "_q
+ QString::number(index.size())
+ u", expected 77"_q;
}
auto ok = false;
const auto asInt64 = index.toLongLong(&ok);
if (ok) {
return u"gift index: an int64 accepted 77 digits as "_q
+ QString::number(asInt64)
+ u", so the value must stay a string"_q;
}
const auto owner = u"api-nft-items-owner1.json"_q;
const auto ownerBytes = ReadFixture(owner);
if (ownerBytes.isEmpty()) {
return u"fixture read failed: "_q + owner;
}
const auto items = ParseNftItems(ownerBytes, 100);
if (!items) {
return u"parse failed: "_q + owner;
}
auto longest = QString();
auto longestCount = 0;
for (const auto &item : items->list) {
if (item.index.size() > longest.size()) {
longest = item.index;
longestCount = 1;
} else if (item.index.size() == longest.size()) {
++longestCount;
}
}
const auto longestExpected =
u"1054708645066200327782942852958057359233"
u"77271801864155263396403385820771314635"_q;
if (int(longest.size()) != 78) {
return u"owner1 longest index size: got "_q
+ QString::number(longest.size())
+ u", expected 78"_q;
} else if (longestCount != 5) {
return u"owner1 78-digit indexes: got "_q
+ QString::number(longestCount)
+ u", expected 5"_q;
} else if (longest != longestExpected) {
return u"owner1 longest index: got "_q
+ longest
+ u", expected "_q
+ longestExpected;
}
return QString();
} },
{ u"nft_items_content_uri_spread"_q, [] {
const auto name = u"api-nft-items-owner1.json"_q;
const auto bytes = ReadFixture(name);
if (bytes.isEmpty()) {
return u"fixture read failed: "_q + name;
}
const auto page = ParseNftItems(bytes, 100);
if (!page) {
return u"parse failed: "_q + name;
} else if (int(page->list.size()) != 70) {
return u"count: got "_q
+ QString::number(page->list.size())
+ u", expected 70"_q;
}
auto withUri = 0;
auto withoutUri = 0;
auto https = 0;
auto http = 0;
auto ipfs = 0;
auto tonstorage = 0;
auto domains = 0;
auto ipfsUri = QString();
auto storageUri = QString();
for (const auto &item : page->list) {
if (item.contentUri.isEmpty()) {
++withoutUri;
if (!item.domain.isEmpty()) {
++domains;
}
} else {
++withUri;
if (item.contentUri.startsWith(u"https://"_q)) {
++https;
} else if (item.contentUri.startsWith(u"http://"_q)) {
++http;
} else if (item.contentUri.startsWith(u"ipfs:"_q)) {
++ipfs;
ipfsUri = item.contentUri;
} else if (item.contentUri.startsWith(u"tonstorage:"_q)) {
++tonstorage;
storageUri = item.contentUri;
}
}
}
if (withUri != 57 || withoutUri != 13) {
return u"uri spread: got "_q
+ QString::number(withUri)
+ u" with a uri and "_q
+ QString::number(withoutUri)
+ u" without, expected 57 and 13"_q;
} else if (https != 50 || http != 5) {
return u"http spread: got "_q
+ QString::number(https)
+ u" https and "_q
+ QString::number(http)
+ u" http, expected 50 and 5"_q;
} else if (ipfs != 1 || tonstorage != 1) {
return u"exotic spread: got "_q
+ QString::number(ipfs)
+ u" ipfs and "_q
+ QString::number(tonstorage)
+ u" tonstorage, expected 1 and 1"_q;
} else if (domains != 12) {
return u"uriless domains: got "_q
+ QString::number(domains)
+ u", expected 12 of the 13 (the 13th carries an image)"_q;
}
const auto expectedIpfs =
u"ipfs://QmZUbAvRbmwbn26fq2312fp67c6iZD2vK1NmwmimWTnA38"_q;
const auto expectedStorage =
u"tonstorage://6BF34B39DC5FD6AC4FF845722E16EA85"
u"74A2504B7C6E1F0DF375805B69AAABE5/2006.json"_q;
if (ipfsUri != expectedIpfs) {
return u"ipfs uri: got "_q
+ ipfsUri
+ u", expected "_q
+ expectedIpfs;
} else if (storageUri != expectedStorage) {
return u"tonstorage uri: got "_q
+ storageUri
+ u", expected "_q
+ expectedStorage;
}
return QString();
} },
{ u"nft_items_null_collection"_q, [] {
const auto owner1 = u"api-nft-items-owner1.json"_q;
const auto firstBytes = ReadFixture(owner1);
if (firstBytes.isEmpty()) {
return u"fixture read failed: "_q + owner1;
}
const auto first = ParseNftItems(firstBytes, 100);
if (!first) {
return u"parse failed: "_q + owner1;
}
auto firstCount = 0;
for (const auto &item : first->list) {
if (!item.collection.isEmpty()) {
continue;
}
++firstCount;
if (item.kind != NftKind::Generic) {
return u"owner1 null-collection: got kind "_q
+ QString::number(int(item.kind))
+ u" for "_q
+ item.address.toUpper()
+ u", expected Generic"_q;
} else if (!item.key.isEmpty()) {
return u"owner1 null-collection: got key "_q
+ item.key
+ u" for "_q
+ item.address.toUpper()
+ u", expected none"_q;
}
}
if (firstCount != 8) {
return u"owner1 null-collection: got "_q
+ QString::number(firstCount)
+ u", expected 8 (the 11 in task.md is the six-capture "
u"total)"_q;
}
const auto owner2 = u"api-nft-items-owner2.json"_q;
const auto secondBytes = ReadFixture(owner2);
if (secondBytes.isEmpty()) {
return u"fixture read failed: "_q + owner2;
}
const auto second = ParseNftItems(secondBytes, 100);
if (!second) {
return u"parse failed: "_q + owner2;
}
auto secondCount = 0;
for (const auto &item : second->list) {
if (!item.collection.isEmpty()) {
continue;
}
++secondCount;
if (item.kind != NftKind::Generic || !item.key.isEmpty()) {
return u"owner2 null-collection: expected Generic with "
u"no key for "_q
+ item.address.toUpper();
}
}
if (secondCount != 3) {
return u"owner2 null-collection: got "_q
+ QString::number(secondCount)
+ u", expected 3"_q;
} else if (firstCount + secondCount != 11) {
return u"owner listings null-collection: got "_q
+ QString::number(firstCount + secondCount)
+ u", expected 11 (8 + 3)"_q;
}
return QString();
} },
{ u"nft_kind_fragment_gift_slug"_q, [] {
const auto name = u"api-nft-items-address-gift.json"_q;
const auto bytes = ReadFixture(name);
if (bytes.isEmpty()) {
return u"fixture read failed: "_q + name;
}
const auto page = ParseNftItems(bytes, 100);
if (!page) {
return u"parse failed: "_q + name;
} else if (int(page->list.size()) != 1) {
return u"count: got "_q
+ QString::number(page->list.size())
+ u", expected 1"_q;
}
const auto uri =
u"https://nft.fragment.com/gift/deskcalendar-45754.json"_q;
const auto &item = page->list.front();
if (item.contentUri != uri) {
return u"contentUri: got "_q
+ item.contentUri
+ u", expected "_q
+ uri;
} else if (item.kind != NftKind::TelegramGift) {
return u"kind: got "_q
+ QString::number(int(item.kind))
+ u", expected TelegramGift; the kind comes from the "
u"content.uri nft.fragment.com/<segment>/ shape, because "
u"each gift model mints its own collection contract and "
u"no address list can enumerate them"_q;
} else if (item.key != u"deskcalendar-45754"_q) {
return u"key: got "_q
+ item.key
+ u", expected deskcalendar-45754; the slug goes verbatim "
u"into payments.getUniqueStarGift, so the server's own "
u"capitalization of it is not derivable from any committed "
u"byte and is deliberately not asserted"_q;
}
const auto nested = QByteArray(
"{\"nft_items\":[{"
"\"address\":\"0:CA0CFD519F763102B5BEC9D9E3AF4359"
"2EA362FB773FA319EA09C4F162C171E0\","
"\"index\":\"1\","
"\"collection_address\":"
"\"0:4C71F300665314AF55B75FC91D130DDF"
"24C5006961F8F9772613947945F14863\","
"\"content\":{\"uri\":\"https://nft.fragment.com/gift/"
"sub/dir.json\"},"
"\"on_sale\":false,"
"\"real_owner\":null"
"}]}");
const auto nestedPage = ParseNftItems(nested, 100);
if (!nestedPage) {
return u"nested slug: parse failed"_q;
} else if (int(nestedPage->list.size()) != 1) {
return u"nested slug: got "_q
+ QString::number(nestedPage->list.size())
+ u" items, expected 1"_q;
}
const auto &nestedItem = nestedPage->list.front();
if (nestedItem.kind != NftKind::Generic) {
return u"nested slug: got kind "_q
+ QString::number(int(nestedItem.kind))
+ u", expected Generic; a remainder that leaves "
u"[a-zA-Z0-9._-] - the only class the app's "
u"t.me/nft/<slug> handler accepts - yields no slug, and "
u"without a resolvable slug the gift kind is useless, so "
u"the item stays Generic"_q;
} else if (!nestedItem.key.isEmpty()) {
return u"nested slug: got key "_q
+ nestedItem.key
+ u", expected none"_q;
}
const auto foreign = QByteArray(
"{\"nft_items\":[{"
"\"address\":\"0:CA0CFD519F763102B5BEC9D9E3AF4359"
"2EA362FB773FA319EA09C4F162C171E0\","
"\"index\":\"1\","
"\"collection_address\":"
"\"0:0C8F3FCC4ABD589206A2CDF1469E3709"
"2C1ADAD272FBE7DB97104569C16F0FF2\","
"\"content\":{\"uri\":\"https://nft.fragment.com/gift/"
"deskcalendar-45754.json\"},"
"\"on_sale\":false,"
"\"real_owner\":null"
"}]}");
const auto synthetic = ParseNftItems(foreign, 100);
if (!synthetic) {
return u"foreign collection: parse failed"_q;
} else if (int(synthetic->list.size()) != 1) {
return u"foreign collection: got "_q
+ QString::number(synthetic->list.size())
+ u" items, expected 1"_q;
}
const auto &other = synthetic->list.front();
if (other.kind != NftKind::TelegramGift) {
return u"foreign collection: got kind "_q
+ QString::number(int(other.kind))
+ u", expected TelegramGift; a gift uri nominates a "
u"candidate whatever its collection, because every gift "
u"model mints its own collection contract. The uri is "
u"item-controlled, so the wallet shows the gift view "
u"only after payments.getUniqueStarGift resolves the "
u"slug and the reply's gift_address equals this exact "
u"item's address; a forged uri fails that binding and "
u"lands in the generic preview fallback"_q;
} else if (other.key != u"deskcalendar-45754"_q) {
return u"foreign collection: got key "_q
+ other.key
+ u", expected deskcalendar-45754"_q;
}
return QString();
} },
{ u"nft_kind_fragment_number_and_username"_q, [] {
const auto numbers = u"api-nft-items-collection-numbers.json"_q;
const auto numbersBytes = ReadFixture(numbers);
if (numbersBytes.isEmpty()) {
return u"fixture read failed: "_q + numbers;
}
const auto numbersPage = ParseNftItems(numbersBytes, 100);
if (!numbersPage) {
return u"parse failed: "_q + numbers;
} else if (int(numbersPage->list.size()) != 1) {
return u"numbers count: got "_q
+ QString::number(numbersPage->list.size())
+ u", expected 1"_q;
}
const auto &number = numbersPage->list.front();
if (number.kind != NftKind::TelegramNumber) {
return u"numbers kind: got "_q
+ QString::number(int(number.kind))
+ u", expected TelegramNumber"_q;
} else if (number.key != u"88807684929"_q) {
return u"numbers key: got "_q
+ number.key
+ u", expected 88807684929"_q;
} else if (!number.domain.isEmpty()) {
return u"numbers domain: got "_q
+ number.domain
+ u", expected none"_q;
} else if (number.onSale) {
return u"numbers onSale: got true, expected false"_q;
}
const auto usernames =
u"api-nft-items-collection-usernames.json"_q;
const auto usernamesBytes = ReadFixture(usernames);
if (usernamesBytes.isEmpty()) {
return u"fixture read failed: "_q + usernames;
}
const auto usernamesPage = ParseNftItems(usernamesBytes, 100);
if (!usernamesPage) {
return u"parse failed: "_q + usernames;
} else if (int(usernamesPage->list.size()) != 1) {
return u"usernames count: got "_q
+ QString::number(usernamesPage->list.size())
+ u", expected 1"_q;
}
const auto owner = u"0:E10225471558A1CBB59CF73C8265CBE2"
u"ACA02F804C1A893992DFF468D21C15E9"_q;
const auto sale = u"0:71315F35CFE3911C7BBC651E11131353"
u"16C7EADF7280EE0105D7E2D9F80D46A7"_q;
const auto &username = usernamesPage->list.front();
if (username.kind != NftKind::TelegramUsername) {
return u"usernames kind: got "_q
+ QString::number(int(username.kind))
+ u", expected TelegramUsername"_q;
} else if (username.key != u"katrinakaif"_q) {
return u"usernames key: got "_q
+ username.key
+ u", expected katrinakaif"_q;
} else if (username.domain != u"katrinakaif.t.me"_q) {
return u"usernames domain: got "_q
+ username.domain
+ u", expected katrinakaif.t.me"_q;
} else if (!username.onSale) {
return u"usernames onSale: got false, expected true"_q;
} else if (username.realOwner.toUpper() == sale) {
return u"usernames realOwner: got the sale contract, so the "
u"parser read owner_address or sale_contract_address "
u"instead of real_owner"_q;
} else if (username.realOwner.toUpper() != owner) {
return u"usernames realOwner: got "_q
+ username.realOwner.toUpper()
+ u", expected "_q
+ owner;
}
const auto forged = QByteArray(
"{\"nft_items\":[{"
"\"address\":\"0:CA0CFD519F763102B5BEC9D9E3AF4359"
"2EA362FB773FA319EA09C4F162C171E0\","
"\"index\":\"1\","
"\"collection_address\":"
"\"0:0C8F3FCC4ABD589206A2CDF1469E3709"
"2C1ADAD272FBE7DB97104569C16F0FF2\","
"\"content\":{\"uri\":\"https://nft.fragment.com/"
"number/88807684929.json\"},"
"\"on_sale\":false,"
"\"real_owner\":null"
"}]}");
const auto forgedPage = ParseNftItems(forged, 100);
if (!forgedPage || int(forgedPage->list.size()) != 1) {
return u"forged number: parse failed"_q;
}
const auto &fake = forgedPage->list.front();
if (fake.kind != NftKind::Generic || !fake.key.isEmpty()) {
return u"forged number: got kind "_q
+ QString::number(int(fake.kind))
+ u" key "_q
+ fake.key
+ u", expected Generic with none; numbers and "
u"usernames have exactly one authoritative Fragment "
u"collection each, so a real-shaped uri under any "
u"other collection is forged metadata and must not "
u"classify"_q;
}
return QString();
} },
{ u"nft_classify_kind_direct"_q, [] {
const auto foreignRaw = u"0:0c8f3fcc4abd589206a2cdf1469e3709"
u"2c1adad272fbe7db97104569c16f0ff2"_q;
auto gift = NftItem();
gift.collection = foreignRaw;
gift.contentUri =
u"https://nft.fragment.com/gift/deskcalendar-45754.json"_q;
ClassifyNftKind(gift);
if (gift.kind != NftKind::TelegramGift) {
return u"gift kind: got "_q
+ QString::number(int(gift.kind))
+ u", expected TelegramGift; every gift model mints its "
u"own collection contract, so the gift uri nominates the "
u"candidate whatever collection holds the item"_q;
} else if (gift.key != u"deskcalendar-45754"_q) {
return u"gift key: got "_q
+ gift.key
+ u", expected deskcalendar-45754"_q;
}
const auto numberRaw = u"0:0e41dc1dc3c9067ed24248580e12b335"
u"9818d83dee0304fabcf80845eafafdb2"_q;
const auto numberUri =
u"https://nft.fragment.com/number/88807684929.json"_q;
auto number = NftItem();
number.collection = numberRaw;
number.contentUri = numberUri;
ClassifyNftKind(number);
if (number.kind != NftKind::TelegramNumber) {
return u"number kind: got "_q
+ QString::number(int(number.kind))
+ u", expected TelegramNumber"_q;
} else if (number.key != u"88807684929"_q) {
return u"number key: got "_q
+ number.key
+ u", expected 88807684929"_q;
}
auto forged = NftItem();
forged.collection = foreignRaw;
forged.contentUri = numberUri;
ClassifyNftKind(forged);
if (forged.kind != NftKind::Generic || !forged.key.isEmpty()) {
return u"forged number: got kind "_q
+ QString::number(int(forged.kind))
+ u" key "_q
+ forged.key
+ u", expected Generic with none; numbers and usernames "
u"each have exactly one authoritative Fragment "
u"collection, so the address still gates them and only "
u"a gift uri nominates without one"_q;
}
return QString();
} },
{ u"nft_classify_kind_media"_q, [] {
const auto foreignRaw = u"0:0c8f3fcc4abd589206a2cdf1469e3709"
u"2c1adad272fbe7db97104569c16f0ff2"_q;
const auto numberRaw = u"0:0e41dc1dc3c9067ed24248580e12b335"
u"9818d83dee0304fabcf80845eafafdb2"_q;
const auto usernameRaw = u"0:80d78a35f955a14b679faa887ff4cd5b"
u"fc0f43b4a4eea2a7e6927f3701b273c2"_q;
const auto document = [](const char *url) {
return NftWebDocument{ .url = QByteArray(url) };
};
const auto expect = [](
const QString &label,
const NftItem &item,
NftKind kind,
const QString &key) {
if (item.kind == kind && item.key == key) {
return QString();
}
return label
+ u": got kind "_q
+ QString::number(int(item.kind))
+ u" key '"_q
+ item.key
+ u"', expected kind "_q
+ QString::number(int(kind))
+ u" key '"_q
+ key
+ u"'"_q;
};
auto result = QString();
const auto check = [&](
const QString &label,
NftItem item,
NftKind kind,
const QString &key) {
ClassifyNftKind(item);
if (result.isEmpty()) {
result = expect(label, item, kind, key);
}
};
auto giftImage = NftItem();
giftImage.collection = foreignRaw;
giftImage.image = document(
"https://nft.fragment.com/gift/deskcalendar-45754.webp");
check(
u"gift from image"_q,
giftImage,
NftKind::TelegramGift,
u"deskcalendar-45754"_q);
auto giftLottie = NftItem();
giftLottie.collection = foreignRaw;
giftLottie.imageSmall = document(
"https://imgproxy.toncenter.com/CLisjZYndcPa0HF7lUeL2LkBthE4"
"L7OJ04Gp8GEDknE/pr:small/aHR0cHM6Ly9uZnQuZnJhZ21lbnQuY29tL"
"2dpZnQvZGVza2NhbGVuZGFyLTQ1NzU0LndlYnA");
giftLottie.lottie = document(
"https://nft.fragment.com/gift/deskcalendar-45754.lottie.json");
check(
u"gift from lottie"_q,
giftLottie,
NftKind::TelegramGift,
u"deskcalendar-45754"_q);
auto numberImage = NftItem();
numberImage.collection = numberRaw;
numberImage.name = u"+888 0768 4929"_q;
numberImage.image = document(
"https://nft.fragment.com/number/88807684929.webp");
check(
u"number from image"_q,
numberImage,
NftKind::TelegramNumber,
u"88807684929"_q);
auto numberName = NftItem();
numberName.collection = numberRaw;
numberName.name = u"+888 0768 4929"_q;
check(
u"number from name"_q,
numberName,
NftKind::TelegramNumber,
u"88807684929"_q);
auto usernameName = NftItem();
usernameName.collection = usernameRaw;
usernameName.name = u"@tolya"_q;
check(
u"username from name"_q,
usernameName,
NftKind::TelegramUsername,
u"tolya"_q);
auto forged = NftItem();
forged.collection = foreignRaw;
forged.name = u"+888 0768 4929"_q;
forged.image = document(
"https://nft.fragment.com/number/88807684929.webp");
check(
u"forged number media"_q,
forged,
NftKind::Generic,
QString());
auto foreignHost = NftItem();
foreignHost.collection = foreignRaw;
foreignHost.image = document("https://example.com/gift/x.webp");
check(
u"foreign host"_q,
foreignHost,
NftKind::Generic,
QString());
auto traversal = NftItem();
traversal.collection = foreignRaw;
traversal.image = document(
"https://nft.fragment.com/gift/../x.webp");
check(
u"dot segment"_q,
traversal,
NftKind::Generic,
QString());
auto encoded = NftItem();
encoded.collection = foreignRaw;
encoded.image = document(
"https://nft.fragment.com/gift/x%2Fy.webp");
check(
u"encoded slash"_q,
encoded,
NftKind::Generic,
QString());
auto bare = NftItem();
bare.collection = foreignRaw;
bare.name = u"Some Item #1"_q;
check(u"bare generic"_q, bare, NftKind::Generic, QString());
return result;
} },
{ u"nft_items_negative"_q, [] {
const auto name = u"api-nft-items-camel.json"_q;
const auto bytes = ReadFixture(name);
if (bytes.isEmpty()) {
return u"fixture read failed: "_q + name;
} else if (ParseNftItems(bytes, 100)) {
return u"expected nullopt for the error body: "_q
+ QString::fromUtf8(bytes);
}
const auto bad = std::vector<QByteArray>{
QByteArray(""),
QByteArray("{"),
QByteArray("[]"),
QByteArray("{}"),
QByteArray("null"),
QByteArray("42"),
QByteArray("{\"nft_items\":{}}"),
QByteArray("{\"nft_items\":[42]}"),
QByteArray("{\"nft_items\":[{}]}"),
};
for (const auto &json : bad) {
if (ParseNftItems(json, 100)) {
return u"expected nullopt for: "_q
+ QString::fromUtf8(json);
}
}
return QString();
} },
};
}
} // namespace Gram::Tests