/*
 *    OPC Services tests
 *
 * Copyright 2018 Nikolay Sivov for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#define COBJMACROS

#include "windows.h"
#include "initguid.h"
#include "msopc.h"
#include "urlmon.h"

#include "wine/heap.h"
#include "wine/test.h"

static IOpcFactory *create_factory(void)
{
    IOpcFactory *factory = NULL;
    CoCreateInstance(&CLSID_OpcFactory, NULL, CLSCTX_INPROC_SERVER, &IID_IOpcFactory, (void **)&factory);
    return factory;
}

static void test_package(void)
{
    static const WCHAR typeW[] = {'t','y','p','e','/','s','u','b','t','y','p','e',0};
    static const WCHAR targetW[] = {'t','a','r','g','e','t',0};
    static const WCHAR uriW[] = {'/','u','r','i',0};
    static const WCHAR rootW[] = {'/',0};
    IOpcRelationshipSet *relset, *relset2;
    IOpcPartUri *part_uri, *part_uri2;
    IOpcPartSet *partset, *partset2;
    OPC_COMPRESSION_OPTIONS options;
    IStream *stream, *stream2;
    IOpcPart *part, *part2;
    IOpcRelationship *rel;
    IOpcFactory *factory;
    IOpcPackage *package;
    LARGE_INTEGER move;
    ULARGE_INTEGER pos;
    IUri *target_uri;
    char buff[16];
    IOpcUri *uri;
    HRESULT hr;
    BSTR str;
    BOOL ret;

    factory = create_factory();

    hr = IOpcFactory_CreatePackage(factory, &package);
    ok(SUCCEEDED(hr) || broken(hr == E_NOTIMPL) /* Vista */, "Failed to create a package, hr %#x.\n", hr);
    if (FAILED(hr))
    {
        IOpcFactory_Release(factory);
        return;
    }

    hr = IOpcPackage_GetPartSet(package, &partset);
    ok(SUCCEEDED(hr), "Failed to create a part set, hr %#x.\n", hr);

    hr = IOpcPackage_GetPartSet(package, &partset2);
    ok(SUCCEEDED(hr), "Failed to create a part set, hr %#x.\n", hr);
    ok(partset == partset2, "Expected same part set instance.\n");
    IOpcPartSet_Release(partset2);

    /* CreatePart */
    hr = IOpcFactory_CreatePartUri(factory, uriW, &part_uri);
    ok(SUCCEEDED(hr), "Failed to create part uri, hr %#x.\n", hr);

    hr = IOpcFactory_CreatePartUri(factory, uriW, &part_uri2);
    ok(SUCCEEDED(hr), "Failed to create part uri, hr %#x.\n", hr);

    part = (void *)0xdeadbeef;
    hr = IOpcPartSet_CreatePart(partset, NULL, typeW, OPC_COMPRESSION_NONE, &part);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);
    ok(part == NULL, "Unexpected pointer %p.\n", part);

    hr = IOpcPartSet_CreatePart(partset, part_uri, typeW, OPC_COMPRESSION_NONE, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    hr = IOpcPartSet_CreatePart(partset, part_uri, typeW, 0xdeadbeef, &part);
    ok(SUCCEEDED(hr), "Failed to create a part, hr %#x.\n", hr);
    hr = IOpcPart_GetCompressionOptions(part, &options);
    ok(SUCCEEDED(hr), "Failed to get compression options, hr %#x.\n", hr);
    ok(options == 0xdeadbeef, "Unexpected compression options %#x.\n", options);

    part2 = (void *)0xdeadbeef;
    hr = IOpcPartSet_CreatePart(partset, part_uri, typeW, OPC_COMPRESSION_NONE, &part2);
    ok(hr == OPC_E_DUPLICATE_PART, "Unexpected hr %#x.\n", hr);
    ok(part2 == NULL, "Unexpected instance %p.\n", part2);

    part2 = (void *)0xdeadbeef;
    hr = IOpcPartSet_CreatePart(partset, part_uri2, typeW, OPC_COMPRESSION_NONE, &part2);
    ok(hr == OPC_E_DUPLICATE_PART, "Unexpected hr %#x.\n", hr);
    ok(part2 == NULL, "Unexpected instance %p.\n", part2);
    IOpcPartUri_Release(part_uri2);

    hr = IOpcPartSet_GetPart(partset, NULL, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    part2 = (void *)0xdeadbeef;
    hr = IOpcPartSet_GetPart(partset, NULL, &part2);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);
    ok(part2 == NULL, "Unexpected pointer %p.\n", part2);

    hr = IOpcPartSet_GetPart(partset, part_uri, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    hr = IOpcPartSet_GetPart(partset, part_uri, &part2);
    ok(SUCCEEDED(hr), "Failed to get part, hr %#x.\n", hr);
    IOpcPart_Release(part2);

    hr = IOpcFactory_CreatePartUri(factory, targetW, &part_uri2);
    ok(SUCCEEDED(hr), "Failed to create part uri, hr %#x.\n", hr);

    hr = IOpcPartSet_GetPart(partset, part_uri2, &part2);
    ok(hr == OPC_E_NO_SUCH_PART, "Unexpected hr %#x.\n", hr);
    IOpcPartUri_Release(part_uri2);

    hr = IOpcPart_GetContentStream(part, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    hr = IOpcPart_GetContentStream(part, &stream);
    ok(SUCCEEDED(hr), "Failed to get content stream, hr %#x.\n", hr);

    hr = IStream_Write(stream, "abc", 3, NULL);
    ok(hr == S_OK, "Failed to write content, hr %#x.\n", hr);

    move.QuadPart = 0;
    hr = IStream_Seek(stream, move, STREAM_SEEK_CUR, &pos);
    ok(SUCCEEDED(hr), "Seek failed, hr %#x.\n", hr);
    ok(pos.QuadPart == 3, "Unexpected position.\n");

    hr = IOpcPart_GetContentStream(part, &stream2);
    ok(SUCCEEDED(hr), "Failed to get content stream, hr %#x.\n", hr);
    ok(stream != stream2, "Unexpected instance.\n");

    move.QuadPart = 0;
    hr = IStream_Seek(stream2, move, STREAM_SEEK_CUR, &pos);
    ok(SUCCEEDED(hr), "Seek failed, hr %#x.\n", hr);
    ok(pos.QuadPart == 0, "Unexpected position.\n");

    memset(buff, 0, sizeof(buff));
    hr = IStream_Read(stream2, buff, sizeof(buff), NULL);
    ok(hr == S_OK, "Failed to read content, hr %#x.\n", hr);
    ok(!memcmp(buff, "abc", 3), "Unexpected content.\n");

    move.QuadPart = 0;
    hr = IStream_Seek(stream2, move, STREAM_SEEK_CUR, &pos);
    ok(SUCCEEDED(hr), "Seek failed, hr %#x.\n", hr);
    ok(pos.QuadPart == 3, "Unexpected position.\n");

    IStream_Release(stream);
    IStream_Release(stream2);

    hr = IOpcPart_GetRelationshipSet(part, &relset);
    ok(SUCCEEDED(hr), "Failed to get relationship set, hr %#x.\n", hr);

    hr = IOpcPart_GetRelationshipSet(part, &relset2);
    ok(SUCCEEDED(hr), "Failed to get relationship set, hr %#x.\n", hr);
    ok(relset == relset2, "Expected same part set instance.\n");

    hr = CreateUri(targetW, Uri_CREATE_ALLOW_RELATIVE, 0, &target_uri);
    ok(SUCCEEDED(hr), "Failed to create target uri, hr %#x.\n", hr);

    hr = IOpcRelationshipSet_CreateRelationship(relset, NULL, typeW, target_uri, OPC_URI_TARGET_MODE_INTERNAL, &rel);
    ok(SUCCEEDED(hr), "Failed to create relationship, hr %#x.\n", hr);

    hr = IOpcRelationship_GetSourceUri(rel, &uri);
    ok(SUCCEEDED(hr), "Failed to get source uri, hr %#x.\n", hr);
    ok(uri == (IOpcUri *)part_uri, "Unexpected source uri.\n");

    IOpcUri_Release(uri);

    IOpcRelationship_Release(rel);
    IUri_Release(target_uri);

    IOpcRelationshipSet_Release(relset);
    IOpcRelationshipSet_Release(relset2);

    ret = 123;
    hr = IOpcPartSet_PartExists(partset, NULL, &ret);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);
    ok(ret == 123, "Unexpected return value.\n");

    hr = IOpcPartSet_PartExists(partset, part_uri, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    ret = FALSE;
    hr = IOpcPartSet_PartExists(partset, part_uri, &ret);
    ok(SUCCEEDED(hr), "Unexpected hr %#x.\n", hr);
    ok(ret, "Expected part to exist.\n");

    IOpcPartUri_Release(part_uri);
    IOpcPart_Release(part);

    /* Relationships */
    hr = IOpcPackage_GetRelationshipSet(package, &relset);
    ok(SUCCEEDED(hr), "Failed to get relationship set, hr %#x.\n", hr);

    hr = IOpcPackage_GetRelationshipSet(package, &relset2);
    ok(SUCCEEDED(hr), "Failed to get relationship set, hr %#x.\n", hr);
    ok(relset == relset2, "Expected same part set instance.\n");
    IOpcRelationshipSet_Release(relset);
    IOpcRelationshipSet_Release(relset2);

    IOpcPartSet_Release(partset);
    IOpcPackage_Release(package);

    /* Root uri */
    hr = IOpcFactory_CreatePackageRootUri(factory, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);
    hr = IOpcFactory_CreatePackageRootUri(factory, &uri);
    ok(SUCCEEDED(hr), "Failed to create root uri, hr %#x.\n", hr);
    hr = IOpcUri_GetRawUri(uri, &str);
    ok(SUCCEEDED(hr), "Failed to get raw uri, hr %#x.\n", hr);
    ok(!lstrcmpW(str, rootW), "Unexpected uri %s.\n", wine_dbgstr_w(str));
    SysFreeString(str);
    IOpcUri_Release(uri);

    IOpcFactory_Release(factory);
}

#define test_stream_stat(stream, size) test_stream_stat_(__LINE__, stream, size)
static void test_stream_stat_(unsigned int line, IStream *stream, ULONG size)
{
    STATSTG statstg;
    HRESULT hr;

    hr = IStream_Stat(stream, NULL, 0);
    ok_(__FILE__, line)(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    memset(&statstg, 0xff, sizeof(statstg));
    hr = IStream_Stat(stream, &statstg, 0);
    ok_(__FILE__, line)(SUCCEEDED(hr), "Failed to get stat info, hr %#x.\n", hr);

    ok_(__FILE__, line)(statstg.pwcsName == NULL, "Unexpected name %s.\n", wine_dbgstr_w(statstg.pwcsName));
    ok_(__FILE__, line)(statstg.type == STGTY_STREAM, "Unexpected type.\n");
    ok_(__FILE__, line)(statstg.cbSize.QuadPart == size, "Unexpected size %u, expected %u.\n",
            statstg.cbSize.LowPart, size);
    ok_(__FILE__, line)(statstg.grfMode == STGM_READ, "Unexpected mode.\n");
    ok_(__FILE__, line)(statstg.grfLocksSupported == 0, "Unexpected lock mode.\n");
    ok_(__FILE__, line)(statstg.grfStateBits == 0, "Unexpected state bits.\n");
}

static void test_file_stream(void)
{
    static const WCHAR filereadW[] = {'o','p','c','f','i','l','e','r','e','a','d','.','e','x','t',0};
    WCHAR temppathW[MAX_PATH], pathW[MAX_PATH];
    IOpcFactory *factory;
    LARGE_INTEGER move;
    IStream *stream;
    char buff[64];
    HRESULT hr;
    ULONG size;

    factory = create_factory();

    hr = IOpcFactory_CreateStreamOnFile(factory, NULL, OPC_STREAM_IO_READ, NULL, 0, &stream);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    GetTempPathW(ARRAY_SIZE(temppathW), temppathW);
    lstrcpyW(pathW, temppathW);
    lstrcatW(pathW, filereadW);
    DeleteFileW(pathW);

    hr = IOpcFactory_CreateStreamOnFile(factory, pathW, OPC_STREAM_IO_READ, NULL, 0, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    /* File does not exist */
    hr = IOpcFactory_CreateStreamOnFile(factory, pathW, OPC_STREAM_IO_READ, NULL, 0, &stream);
    ok(FAILED(hr), "Unexpected hr %#x.\n", hr);

    hr = IOpcFactory_CreateStreamOnFile(factory, pathW, OPC_STREAM_IO_WRITE, NULL, 0, &stream);
    ok(SUCCEEDED(hr), "Failed to create a write stream, hr %#x.\n", hr);

    test_stream_stat(stream, 0);

    size = lstrlenW(pathW) * sizeof(WCHAR);
    hr = IStream_Write(stream, pathW, size, NULL);
    ok(hr == S_OK, "Stream write failed, hr %#x.\n", hr);

    test_stream_stat(stream, size);
    IStream_Release(stream);

    /* Invalid I/O mode */
    hr = IOpcFactory_CreateStreamOnFile(factory, pathW, 10, NULL, 0, &stream);
    ok(hr == E_INVALIDARG, "Failed to create a write stream, hr %#x.\n", hr);

    /* Write to read-only stream. */
    hr = IOpcFactory_CreateStreamOnFile(factory, pathW, OPC_STREAM_IO_READ, NULL, 0, &stream);
    ok(SUCCEEDED(hr), "Failed to create a read stream, hr %#x.\n", hr);

    test_stream_stat(stream, size);
    hr = IStream_Write(stream, pathW, size, NULL);
    ok(hr == HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED), "Stream write failed, hr %#x.\n", hr);
    IStream_Release(stream);

    /* Read from write-only stream. */
    hr = IOpcFactory_CreateStreamOnFile(factory, pathW, OPC_STREAM_IO_WRITE, NULL, 0, &stream);
    ok(SUCCEEDED(hr), "Failed to create a read stream, hr %#x.\n", hr);

    test_stream_stat(stream, 0);
    hr = IStream_Write(stream, pathW, size, NULL);
    ok(hr == S_OK, "Stream write failed, hr %#x.\n", hr);
    test_stream_stat(stream, size);

    move.QuadPart = 0;
    hr = IStream_Seek(stream, move, STREAM_SEEK_SET, NULL);
    ok(SUCCEEDED(hr), "Seek failed, hr %#x.\n", hr);

    hr = IStream_Read(stream, buff, sizeof(buff), NULL);
    ok(hr == HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED), "Stream read failed, hr %#x.\n", hr);

    IStream_Release(stream);

    IOpcFactory_Release(factory);
    DeleteFileW(pathW);
}

static void test_relationship(void)
{
    static const WCHAR absoluteW[] = {'f','i','l','e',':','/','/','h','o','s','t','/','f','i','l','e','.','t','x','t',0};
    static const WCHAR targetW[] = {'t','a','r','g','e','t',0};
    static const WCHAR typeW[] = {'t','y','p','e',0};
    static const WCHAR rootW[] = {'/',0};
    IUri *target_uri, *target_uri2, *uri;
    IOpcRelationship *rel, *rel2, *rel3;
    IOpcUri *source_uri, *source_uri2;
    IOpcRelationshipSet *rels;
    IOpcFactory *factory;
    IOpcPackage *package;
    IUnknown *unk;
    DWORD mode;
    HRESULT hr;
    WCHAR *id;
    BOOL ret;
    BSTR str;

    factory = create_factory();

    hr = IOpcFactory_CreatePackage(factory, &package);
    ok(SUCCEEDED(hr) || broken(hr == E_NOTIMPL) /* Vista */, "Failed to create a package, hr %#x.\n", hr);
    if (FAILED(hr))
    {
        IOpcFactory_Release(factory);
        return;
    }

    hr = CreateUri(targetW, Uri_CREATE_ALLOW_RELATIVE, 0, &target_uri);
    ok(SUCCEEDED(hr), "Failed to create target uri, hr %#x.\n", hr);

    hr = CreateUri(absoluteW, 0, 0, &target_uri2);
    ok(SUCCEEDED(hr), "Failed to create target uri, hr %#x.\n", hr);

    hr = IOpcPackage_GetRelationshipSet(package, &rels);
    ok(SUCCEEDED(hr), "Failed to get part set, hr %#x.\n", hr);

    rel = (void *)0xdeadbeef;
    hr = IOpcRelationshipSet_CreateRelationship(rels, NULL, NULL, NULL, OPC_URI_TARGET_MODE_INTERNAL, &rel);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);
    ok(rel == NULL, "Unexpected instance %p.\n", rel);

    rel = (void *)0xdeadbeef;
    hr = IOpcRelationshipSet_CreateRelationship(rels, NULL, typeW, NULL, OPC_URI_TARGET_MODE_INTERNAL, &rel);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);
    ok(rel == NULL, "Unexpected instance %p.\n", rel);

    hr = IOpcRelationshipSet_CreateRelationship(rels, NULL, NULL, target_uri, OPC_URI_TARGET_MODE_INTERNAL, &rel);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    /* Absolute target uri with internal mode */
    rel = (void *)0xdeadbeef;
    hr = IOpcRelationshipSet_CreateRelationship(rels, NULL, typeW, target_uri2, OPC_URI_TARGET_MODE_INTERNAL, &rel);
    ok(hr == OPC_E_INVALID_RELATIONSHIP_TARGET, "Unexpected hr %#x.\n", hr);
    ok(rel == NULL, "Unexpected instance %p.\n", rel);

    hr = IOpcRelationshipSet_CreateRelationship(rels, NULL, typeW, target_uri, OPC_URI_TARGET_MODE_INTERNAL, &rel);
    ok(SUCCEEDED(hr), "Failed to create relationship, hr %#x.\n", hr);

    /* Autogenerated relationship id */
    hr = IOpcRelationship_GetId(rel, &id);
    ok(SUCCEEDED(hr), "Failed to get id, hr %#x.\n", hr);
    ok(lstrlenW(id) == 9 && *id == 'R', "Unexpected relationship id %s.\n", wine_dbgstr_w(id));

    hr = IOpcRelationshipSet_CreateRelationship(rels, id, typeW, target_uri, OPC_URI_TARGET_MODE_INTERNAL, &rel2);
    ok(hr == OPC_E_DUPLICATE_RELATIONSHIP, "Failed to create relationship, hr %#x.\n", hr);

    hr = IOpcRelationshipSet_CreateRelationship(rels, id, typeW, target_uri2, OPC_URI_TARGET_MODE_INTERNAL, &rel2);
    ok(hr == OPC_E_DUPLICATE_RELATIONSHIP, "Failed to create relationship, hr %#x.\n", hr);

    hr = IOpcRelationshipSet_CreateRelationship(rels, NULL, typeW, target_uri, OPC_URI_TARGET_MODE_INTERNAL, &rel2);
    ok(SUCCEEDED(hr), "Failed to create relationship, hr %#x.\n", hr);

    ret = 123;
    hr = IOpcRelationshipSet_RelationshipExists(rels, NULL, &ret);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);
    ok(ret == 123, "Unexpected result %d.\n", ret);

    hr = IOpcRelationshipSet_RelationshipExists(rels, id, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    ret = FALSE;
    hr = IOpcRelationshipSet_RelationshipExists(rels, id, &ret);
    ok(SUCCEEDED(hr), "Failed to get relationship, hr %#x.\n", hr);
    ok(ret, "Unexpected result %d.\n", ret);

    hr = IOpcRelationshipSet_GetRelationship(rels, id, &rel3);
    ok(SUCCEEDED(hr), "Failed to get relationship, hr %#x.\n", hr);
    IOpcRelationship_Release(rel3);

    hr = IOpcRelationshipSet_GetRelationship(rels, id, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    rel3 = (void *)0xdeadbeef;
    hr = IOpcRelationshipSet_GetRelationship(rels, NULL, &rel3);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);
    ok(rel3 == NULL, "Expected null pointer.\n");

    *id = 'r';
    rel3 = (void *)0xdeadbeef;
    hr = IOpcRelationshipSet_GetRelationship(rels, id, &rel3);
    ok(hr == OPC_E_NO_SUCH_RELATIONSHIP, "Unexpected hr %#x.\n", hr);
    ok(rel3 == NULL, "Expected null pointer.\n");

    ret = TRUE;
    hr = IOpcRelationshipSet_RelationshipExists(rels, id, &ret);
    ok(SUCCEEDED(hr), "Unexpected hr %#x.\n", hr);
    ok(!ret, "Unexpected result %d.\n", ret);

    CoTaskMemFree(id);

    hr = IOpcRelationship_GetTargetUri(rel, &uri);
    ok(SUCCEEDED(hr), "Failed to get target uri, hr %#x.\n", hr);
    ok(uri == target_uri, "Unexpected uri.\n");
    IUri_Release(uri);

    hr = IOpcRelationship_GetTargetMode(rel, &mode);
    ok(SUCCEEDED(hr), "Failed to get target mode, hr %#x.\n", hr);
    ok(mode == OPC_URI_TARGET_MODE_INTERNAL, "Unexpected mode %d.\n", mode);

    /* Source uri */
    hr = IOpcFactory_CreatePackageRootUri(factory, &source_uri);
    ok(SUCCEEDED(hr), "Failed to create root uri, hr %#x.\n", hr);

    hr = IOpcFactory_CreatePackageRootUri(factory, &source_uri2);
    ok(SUCCEEDED(hr), "Failed to create root uri, hr %#x.\n", hr);
    ok(source_uri != source_uri2, "Unexpected uri instance.\n");

    IOpcUri_Release(source_uri);
    IOpcUri_Release(source_uri2);

    hr = IOpcRelationship_GetSourceUri(rel, &source_uri);
    ok(SUCCEEDED(hr), "Failed to get source uri, hr %#x.\n", hr);

    hr = IOpcUri_QueryInterface(source_uri, &IID_IOpcPartUri, (void **)&unk);
    ok(hr == E_NOINTERFACE, "Unexpected hr %#x.\n", hr);

    str = NULL;
    hr = IOpcUri_GetRawUri(source_uri, &str);
    ok(SUCCEEDED(hr), "Failed to get raw uri, hr %#x.\n", hr);
    ok(!lstrcmpW(rootW, str), "Unexpected uri %s.\n", wine_dbgstr_w(str));
    SysFreeString(str);

    hr = IOpcRelationship_GetSourceUri(rel2, &source_uri2);
    ok(SUCCEEDED(hr), "Failed to get source uri, hr %#x.\n", hr);
    ok(source_uri2 == source_uri, "Unexpected source uri.\n");

    IOpcUri_Release(source_uri2);
    IOpcUri_Release(source_uri);

    IOpcRelationship_Release(rel2);
    IOpcRelationship_Release(rel);

    IOpcRelationshipSet_Release(rels);

    IUri_Release(target_uri);
    IUri_Release(target_uri2);
    IOpcPackage_Release(package);
    IOpcFactory_Release(factory);
}

static WCHAR *strdupAtoW(const char *str)
{
    WCHAR *ret = NULL;
    DWORD len;

    if (!str) return ret;
    len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
    ret = heap_alloc(len * sizeof(WCHAR));
    if (ret)
        MultiByteToWideChar(CP_ACP, 0, str, -1, ret, len);
    return ret;
}

static void test_rel_part_uri(void)
{
    static const struct
    {
        const char *uri;
        const char *rel_uri;
        HRESULT hr;
    } rel_part_uri_tests[] =
    {
        { "/uri", "/_rels/uri.rels" },
        { "/path/uri", "/path/_rels/uri.rels" },
        { "path/uri", "/path/_rels/uri.rels" },
        { "../path/uri", "/path/_rels/uri.rels" },
        { "../../path/uri", "/path/_rels/uri.rels" },
        { "/uri.ext", "/_rels/uri.ext.rels" },
        { "/", "/_rels/.rels" },
        { "uri", "/_rels/uri.rels" },
        { "/path/../uri", "/_rels/uri.rels" },
        { "/path/path/../../uri", "/_rels/uri.rels" },
        { "/_rels/uri.ext.rels", "", OPC_E_NONCONFORMING_URI },
    };
    static const struct
    {
        const char *uri;
        BOOL ret;
    } is_rel_part_tests[] =
    {
        { "/uri", FALSE },
        { "uri", FALSE },
        { "/_rels/uri", FALSE },
        { "/_rels/uri/uri", FALSE },
        { "/_rels/uri/uri.rels", FALSE },
        { "/uri/uri.rels", FALSE },
        { "/uri/_rels/uri.rels", TRUE },
        { "/_rels/.rels", TRUE },
    };
    static const WCHAR testuriW[] = {'/','u','r','i',0};
    IOpcPartUri *part_uri;
    IOpcFactory *factory;
    IOpcUri *source_uri;
    unsigned int i;
    WCHAR *uriW;
    HRESULT hr;

    factory = create_factory();

    hr = IOpcFactory_CreatePartUri(factory, testuriW, &part_uri);
    ok(SUCCEEDED(hr), "Failed to create part uri, hr %#x.\n", hr);

    hr = IOpcPartUri_GetRelationshipsPartUri(part_uri, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    hr = IOpcPartUri_IsRelationshipsPartUri(part_uri, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    hr = IOpcPartUri_GetSourceUri(part_uri, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    source_uri = (void *)0xdeadbeef;
    hr = IOpcPartUri_GetSourceUri(part_uri, &source_uri);
    ok(hr == OPC_E_RELATIONSHIP_URI_REQUIRED, "Unexpected hr %#x.\n", hr);
    ok(source_uri == NULL, "Expected null uri.\n");

    IOpcPartUri_Release(part_uri);

    for (i = 0; i < ARRAY_SIZE(rel_part_uri_tests); ++i)
    {
        BOOL is_root = FALSE;
        IOpcPartUri *rel_uri;
        IOpcUri *part_uri;
        WCHAR *rel_uriW;

        uriW = strdupAtoW(rel_part_uri_tests[i].uri);
        rel_uriW = strdupAtoW(rel_part_uri_tests[i].rel_uri);

        if (!strcmp(rel_part_uri_tests[i].uri, "/"))
        {
            hr = IOpcFactory_CreatePackageRootUri(factory, &part_uri);
            is_root = TRUE;
        }
        else
            hr = IOpcFactory_CreatePartUri(factory, uriW, (IOpcPartUri **)&part_uri);
        ok(SUCCEEDED(hr), "Failed to create part uri, hr %#x.\n", hr);

        rel_uri = (void *)0xdeadbeef;
        hr = IOpcUri_GetRelationshipsPartUri(part_uri, &rel_uri);
        if (SUCCEEDED(hr))
        {
            IOpcPartUri *rel_uri2;
            IOpcUri *source_uri2;
            IUnknown *unk = NULL;
            BOOL ret;
            BSTR str;

            hr = IOpcPartUri_GetSourceUri(rel_uri, &source_uri);
            ok(SUCCEEDED(hr), "Failed to get source uri, hr %#x.\n", hr);
            hr = IOpcPartUri_GetSourceUri(rel_uri, &source_uri2);
            ok(SUCCEEDED(hr), "Failed to get source uri, hr %#x.\n", hr);
            ok(source_uri != source_uri2, "Unexpected instance.\n");

            hr = IOpcUri_IsEqual(source_uri, NULL, NULL);
            ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

            ret = 123;
            hr = IOpcUri_IsEqual(source_uri, NULL, &ret);
            ok(is_root ? hr == E_POINTER : hr == S_OK, "Unexpected hr %#x.\n", hr);
            ok(is_root ? ret == 123 : !ret, "Unexpected result.\n");

            ret = FALSE;
            hr = IOpcUri_IsEqual(source_uri, (IUri *)source_uri2, &ret);
            ok(SUCCEEDED(hr), "IsEqual failed, hr %#x.\n", hr);
            ok(ret, "Expected equal uris.\n");

            hr = IOpcUri_QueryInterface(source_uri, &IID_IOpcPartUri, (void **)&unk);
            ok(hr == (is_root ? E_NOINTERFACE : S_OK), "Unexpected hr %#x, %s.\n", hr, rel_part_uri_tests[i].uri);
            if (unk)
                IUnknown_Release(unk);

            IOpcUri_Release(source_uri2);
            IOpcUri_Release(source_uri);

            hr = IOpcUri_GetRelationshipsPartUri(part_uri, &rel_uri2);
            ok(SUCCEEDED(hr), "Failed to get rels part uri, hr %#x.\n", hr);
            ok(rel_uri2 != rel_uri, "Unexpected instance.\n");
            IOpcPartUri_Release(rel_uri2);

            hr = IOpcPartUri_GetRawUri(rel_uri, &str);
            ok(SUCCEEDED(hr), "Failed to get rel uri, hr %#x.\n", hr);
        todo_wine_if(i == 3 || i == 4 || i == 8 || i == 9)
            ok(!lstrcmpW(str, rel_uriW), "%u: unexpected rel uri %s, expected %s.\n", i, wine_dbgstr_w(str),
                    wine_dbgstr_w(rel_uriW));
            SysFreeString(str);

            IOpcPartUri_Release(rel_uri);
        }
        else
        {
            ok(hr == rel_part_uri_tests[i].hr, "%u: unexpected hr %#x.\n", i, hr);
            ok(rel_uri == NULL, "%u: unexpected out pointer.\n", i);
        }

        heap_free(uriW);
        heap_free(rel_uriW);

        IOpcUri_Release(part_uri);
    }

    for (i = 0; i < ARRAY_SIZE(is_rel_part_tests); ++i)
    {
        IOpcPartUri *part_uri;
        BOOL ret;

        uriW = strdupAtoW(is_rel_part_tests[i].uri);

        hr = IOpcFactory_CreatePartUri(factory, uriW, &part_uri);
        ok(SUCCEEDED(hr), "Failed to create part uri, hr %#x.\n", hr);

        ret = 123;
        hr = IOpcPartUri_IsRelationshipsPartUri(part_uri, &ret);
        ok(SUCCEEDED(hr), "Unexpected hr %#x.\n", hr);
        ok(ret == is_rel_part_tests[i].ret, "%u: unexpected result %d.\n", i, ret);

        heap_free(uriW);

        IOpcPartUri_Release(part_uri);
    }

    IOpcFactory_Release(factory);
}

static void test_part_enumerator(void)
{
    static const WCHAR typeW[] = {'t','y','p','e','/','s','u','b','t','y','p','e',0};
    static const WCHAR uriW[] = {'/','u','r','i',0};
    IOpcPartEnumerator *partenum, *partenum2;
    IOpcPart *part, *part2;
    IOpcPartUri *part_uri;
    IOpcPackage *package;
    IOpcFactory *factory;
    IOpcPartSet *parts;
    HRESULT hr;
    BOOL ret;

    factory = create_factory();

    hr = IOpcFactory_CreatePackage(factory, &package);
    ok(SUCCEEDED(hr) || broken(hr == E_NOTIMPL) /* Vista */, "Failed to create a package, hr %#x.\n", hr);
    if (FAILED(hr))
    {
        IOpcFactory_Release(factory);
        return;
    }

    hr = IOpcPackage_GetPartSet(package, &parts);
    ok(SUCCEEDED(hr), "Failed to get part set, hr %#x.\n", hr);

    hr = IOpcPartSet_GetEnumerator(parts, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    hr = IOpcPartSet_GetEnumerator(parts, &partenum);
    ok(SUCCEEDED(hr), "Failed to get enumerator, hr %#x.\n", hr);

    hr = IOpcPartSet_GetEnumerator(parts, &partenum2);
    ok(SUCCEEDED(hr), "Failed to get enumerator, hr %#x.\n", hr);
    ok(partenum != partenum2, "Unexpected instance.\n");
    IOpcPartEnumerator_Release(partenum2);

    hr = IOpcPartEnumerator_GetCurrent(partenum, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    hr = IOpcPartEnumerator_GetCurrent(partenum, &part);
    ok(hr == OPC_E_ENUM_INVALID_POSITION, "Unexpected hr %#x.\n", hr);

    hr = IOpcPartEnumerator_MoveNext(partenum, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    ret = TRUE;
    hr = IOpcPartEnumerator_MoveNext(partenum, &ret);
    ok(hr == S_OK, "Failed to move, hr %#x.\n", hr);
    ok(!ret, "Unexpected result %d.\n", ret);

    ret = TRUE;
    hr = IOpcPartEnumerator_MovePrevious(partenum, &ret);
    ok(hr == S_OK, "Failed to move, hr %#x.\n", hr);
    ok(!ret, "Unexpected result %d.\n", ret);

    hr = IOpcFactory_CreatePartUri(factory, uriW, &part_uri);
    ok(SUCCEEDED(hr), "Failed to create part uri, hr %#x.\n", hr);

    hr = IOpcPartSet_CreatePart(parts, part_uri, typeW, OPC_COMPRESSION_NONE, &part);
    ok(SUCCEEDED(hr), "Failed to create a part, hr %#x.\n", hr);
    IOpcPartUri_Release(part_uri);

    part2 = (void *)0xdeadbeef;
    hr = IOpcPartEnumerator_GetCurrent(partenum, &part2);
    ok(hr == OPC_E_ENUM_COLLECTION_CHANGED, "Unexpected hr %#x.\n", hr);
    ok(part2 == NULL, "Unexpected instance.\n");

    hr = IOpcPartEnumerator_MoveNext(partenum, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    ret = 123;
    hr = IOpcPartEnumerator_MoveNext(partenum, &ret);
    ok(hr == OPC_E_ENUM_COLLECTION_CHANGED, "Unexpected hr %#x.\n", hr);
    ok(ret == 123, "Unexpected result %d.\n", ret);

    hr = IOpcPartEnumerator_MovePrevious(partenum, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    ret = 123;
    hr = IOpcPartEnumerator_MovePrevious(partenum, &ret);
    ok(hr == OPC_E_ENUM_COLLECTION_CHANGED, "Unexpected hr %#x.\n", hr);
    ok(ret == 123, "Unexpected result %d.\n", ret);

    hr = IOpcPartEnumerator_Clone(partenum, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    partenum2 = (void *)0xdeadbeef;
    hr = IOpcPartEnumerator_Clone(partenum, &partenum2);
    ok(hr == OPC_E_ENUM_COLLECTION_CHANGED, "Unexpected hr %#x.\n", hr);
    ok(partenum2 == NULL, "Unexpected instance.\n");

    IOpcPartEnumerator_Release(partenum);

    hr = IOpcPartSet_GetEnumerator(parts, &partenum);
    ok(SUCCEEDED(hr), "Failed to get enumerator, hr %#x.\n", hr);

    part2 = (void *)0xdeadbeef;
    hr = IOpcPartEnumerator_GetCurrent(partenum, &part2);
    ok(hr == OPC_E_ENUM_INVALID_POSITION, "Unexpected hr %#x.\n", hr);
    ok(part2 == NULL, "Unexpected instance.\n");

    hr = IOpcPartEnumerator_MoveNext(partenum, &ret);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    ok(ret, "Unexpected result %d.\n", ret);

    hr = IOpcPartEnumerator_GetCurrent(partenum, &part2);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    ok(part2 == part, "Unexpected instance.\n");
    IOpcPart_Release(part2);

    hr = IOpcPartEnumerator_MoveNext(partenum, &ret);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    ok(!ret, "Unexpected result %d.\n", ret);

    part2 = (void *)0xdeadbeef;
    hr = IOpcPartEnumerator_GetCurrent(partenum, &part2);
    ok(hr == OPC_E_ENUM_INVALID_POSITION, "Unexpected hr %#x.\n", hr);
    ok(part2 == NULL, "Unexpected instance.\n");

    hr = IOpcPartEnumerator_MovePrevious(partenum, &ret);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    ok(ret, "Unexpected result %d.\n", ret);

    hr = IOpcPartEnumerator_GetCurrent(partenum, &part2);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    ok(part2 == part, "Unexpected instance.\n");
    IOpcPart_Release(part2);

    hr = IOpcPartEnumerator_MovePrevious(partenum, &ret);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    ok(!ret, "Unexpected result %d.\n", ret);

    hr = IOpcPartEnumerator_GetCurrent(partenum, &part2);
    ok(hr == OPC_E_ENUM_INVALID_POSITION, "Unexpected hr %#x.\n", hr);

    hr = IOpcPartEnumerator_Clone(partenum, &partenum2);
    ok(SUCCEEDED(hr), "Clone failed, hr %#x.\n", hr);

    hr = IOpcPartEnumerator_MoveNext(partenum2, &ret);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    ok(ret, "Unexpected result %d.\n", ret);

    hr = IOpcPartEnumerator_GetCurrent(partenum2, &part2);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    IOpcPart_Release(part2);

    hr = IOpcPartEnumerator_GetCurrent(partenum, &part2);
    ok(hr == OPC_E_ENUM_INVALID_POSITION, "Unexpected hr %#x.\n", hr);

    IOpcPartEnumerator_Release(partenum2);

    IOpcPartEnumerator_Release(partenum);

    IOpcPart_Release(part);

    IOpcPartSet_Release(parts);

    IOpcPackage_Release(package);
    IOpcFactory_Release(factory);
}

static void test_rels_enumerator(void)
{
    static const WCHAR typeW[] = {'t','y','p','e','/','s','u','b','t','y','p','e',0};
    static const WCHAR targetW[] = {'t','a','r','g','e','t',0};
    IOpcRelationshipEnumerator *relsenum, *relsenum2;
    IOpcRelationship *rel, *rel2;
    IOpcPackage *package;
    IOpcFactory *factory;
    IOpcRelationshipSet *rels;
    IUri *target_uri;
    HRESULT hr;
    BOOL ret;

    factory = create_factory();

    hr = IOpcFactory_CreatePackage(factory, &package);
    ok(SUCCEEDED(hr) || broken(hr == E_NOTIMPL) /* Vista */, "Failed to create a package, hr %#x.\n", hr);
    if (FAILED(hr))
    {
        IOpcFactory_Release(factory);
        return;
    }

    hr = IOpcPackage_GetRelationshipSet(package, &rels);
    ok(SUCCEEDED(hr), "Failed to get part set, hr %#x.\n", hr);

    hr = IOpcRelationshipSet_GetEnumerator(rels, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    hr = IOpcRelationshipSet_GetEnumerator(rels, &relsenum);
    ok(SUCCEEDED(hr), "Failed to get enumerator, hr %#x.\n", hr);

    hr = IOpcRelationshipSet_GetEnumerator(rels, &relsenum2);
    ok(SUCCEEDED(hr), "Failed to get enumerator, hr %#x.\n", hr);
    ok(relsenum != relsenum2, "Unexpected instance.\n");
    IOpcRelationshipEnumerator_Release(relsenum2);

    hr = IOpcRelationshipEnumerator_GetCurrent(relsenum, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    hr = IOpcRelationshipEnumerator_GetCurrent(relsenum, &rel);
    ok(hr == OPC_E_ENUM_INVALID_POSITION, "Unexpected hr %#x.\n", hr);

    hr = IOpcRelationshipEnumerator_MoveNext(relsenum, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    ret = TRUE;
    hr = IOpcRelationshipEnumerator_MoveNext(relsenum, &ret);
    ok(hr == S_OK, "Failed to move, hr %#x.\n", hr);
    ok(!ret, "Unexpected result %d.\n", ret);

    ret = TRUE;
    hr = IOpcRelationshipEnumerator_MovePrevious(relsenum, &ret);
    ok(hr == S_OK, "Failed to move, hr %#x.\n", hr);
    ok(!ret, "Unexpected result %d.\n", ret);

    hr = CreateUri(targetW, Uri_CREATE_ALLOW_RELATIVE, 0, &target_uri);
    ok(SUCCEEDED(hr), "Failed to create target uri, hr %#x.\n", hr);

    hr = IOpcRelationshipSet_CreateRelationship(rels, NULL, typeW, target_uri, OPC_URI_TARGET_MODE_INTERNAL, &rel);
    ok(SUCCEEDED(hr), "Failed to create relationship, hr %#x.\n", hr);

    IUri_Release(target_uri);

    rel2 = (void *)0xdeadbeef;
    hr = IOpcRelationshipEnumerator_GetCurrent(relsenum, &rel2);
    ok(hr == OPC_E_ENUM_COLLECTION_CHANGED, "Unexpected hr %#x.\n", hr);
    ok(rel2 == NULL, "Unexpected instance.\n");

    hr = IOpcRelationshipEnumerator_MoveNext(relsenum, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    ret = 123;
    hr = IOpcRelationshipEnumerator_MoveNext(relsenum, &ret);
    ok(hr == OPC_E_ENUM_COLLECTION_CHANGED, "Unexpected hr %#x.\n", hr);
    ok(ret == 123, "Unexpected result %d.\n", ret);

    hr = IOpcRelationshipEnumerator_MovePrevious(relsenum, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    ret = 123;
    hr = IOpcRelationshipEnumerator_MovePrevious(relsenum, &ret);
    ok(hr == OPC_E_ENUM_COLLECTION_CHANGED, "Unexpected hr %#x.\n", hr);
    ok(ret == 123, "Unexpected result %d.\n", ret);

    hr = IOpcRelationshipEnumerator_Clone(relsenum, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    relsenum2 = (void *)0xdeadbeef;
    hr = IOpcRelationshipEnumerator_Clone(relsenum, &relsenum2);
    ok(hr == OPC_E_ENUM_COLLECTION_CHANGED, "Unexpected hr %#x.\n", hr);
    ok(relsenum2 == NULL, "Unexpected instance.\n");

    IOpcRelationshipEnumerator_Release(relsenum);

    hr = IOpcRelationshipSet_GetEnumerator(rels, &relsenum);
    ok(SUCCEEDED(hr), "Failed to get enumerator, hr %#x.\n", hr);

    rel2 = (void *)0xdeadbeef;
    hr = IOpcRelationshipEnumerator_GetCurrent(relsenum, &rel2);
    ok(hr == OPC_E_ENUM_INVALID_POSITION, "Unexpected hr %#x.\n", hr);
    ok(rel2 == NULL, "Unexpected instance.\n");

    hr = IOpcRelationshipEnumerator_MoveNext(relsenum, &ret);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    ok(ret, "Unexpected result %d.\n", ret);

    hr = IOpcRelationshipEnumerator_GetCurrent(relsenum, &rel2);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    ok(rel2 == rel, "Unexpected instance.\n");
    IOpcRelationship_Release(rel2);

    hr = IOpcRelationshipEnumerator_MoveNext(relsenum, &ret);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    ok(!ret, "Unexpected result %d.\n", ret);

    rel2 = (void *)0xdeadbeef;
    hr = IOpcRelationshipEnumerator_GetCurrent(relsenum, &rel2);
    ok(hr == OPC_E_ENUM_INVALID_POSITION, "Unexpected hr %#x.\n", hr);
    ok(rel2 == NULL, "Unexpected instance.\n");

    hr = IOpcRelationshipEnumerator_MovePrevious(relsenum, &ret);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    ok(ret, "Unexpected result %d.\n", ret);

    hr = IOpcRelationshipEnumerator_GetCurrent(relsenum, &rel2);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    ok(rel2 == rel, "Unexpected instance.\n");
    IOpcRelationship_Release(rel2);

    hr = IOpcRelationshipEnumerator_MovePrevious(relsenum, &ret);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    ok(!ret, "Unexpected result %d.\n", ret);

    hr = IOpcRelationshipEnumerator_GetCurrent(relsenum, &rel2);
    ok(hr == OPC_E_ENUM_INVALID_POSITION, "Unexpected hr %#x.\n", hr);

    hr = IOpcRelationshipEnumerator_Clone(relsenum, &relsenum2);
    ok(SUCCEEDED(hr), "Clone failed, hr %#x.\n", hr);

    hr = IOpcRelationshipEnumerator_MoveNext(relsenum2, &ret);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    ok(ret, "Unexpected result %d.\n", ret);

    hr = IOpcRelationshipEnumerator_GetCurrent(relsenum2, &rel2);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    IOpcRelationship_Release(rel2);

    hr = IOpcRelationshipEnumerator_GetCurrent(relsenum, &rel2);
    ok(hr == OPC_E_ENUM_INVALID_POSITION, "Unexpected hr %#x.\n", hr);

    IOpcRelationshipEnumerator_Release(relsenum2);

    IOpcRelationshipEnumerator_Release(relsenum);

    IOpcRelationship_Release(rel);

    IOpcRelationshipSet_Release(rels);

    IOpcPackage_Release(package);
    IOpcFactory_Release(factory);
}

static void test_relative_uri(void)
{
    static const struct
    {
        const char *part;
        const char *combined;
        const char *relative;
        const char *relative_broken;
    }
    relative_uri_tests[] =
    {
        { "/", "/path/path2", "path/path2", "/path/path2" },
        { "/", "/path", "path", "/path" },
        { "/path/path2", "/path/path2/path3", "path2/path3" },
        { "/path/path2", "/path3", "../path3" },
        { "/path", "/path", "" },
        { "/path", "../path", "" },
        { "/path2", "/path", "path" },
        { "../path", "/path", "" },
        { "../../path", "/path", "" },
    };
    IOpcFactory *factory;
    unsigned int i;

    factory = create_factory();

    for (i = 0; i < ARRAY_SIZE(relative_uri_tests); ++i)
    {
        WCHAR *uriW, *combinedW, *relativeW, *relative_broken_W;
        IOpcPartUri *combined_uri;
        IUri *relative_uri;
        IOpcUri *part_uri;
        IUnknown *unk;
        HRESULT hr;
        BSTR str;

        uriW = strdupAtoW(relative_uri_tests[i].part);
        combinedW = strdupAtoW(relative_uri_tests[i].combined);
        relativeW = strdupAtoW(relative_uri_tests[i].relative);
        relative_broken_W = strdupAtoW(relative_uri_tests[i].relative_broken);

        if (!strcmp(relative_uri_tests[i].part, "/"))
            hr = IOpcFactory_CreatePackageRootUri(factory, &part_uri);
        else
            hr = IOpcFactory_CreatePartUri(factory, uriW, (IOpcPartUri **)&part_uri);
        ok(SUCCEEDED(hr), "%u: failed to create part uri, hr %#x.\n", i, hr);

        hr = IOpcFactory_CreatePartUri(factory, combinedW, &combined_uri);
        ok(SUCCEEDED(hr), "%u: failed to create part uri, hr %#x.\n", i, hr);

        hr = IOpcUri_GetRelativeUri(part_uri, combined_uri, &relative_uri);
    todo_wine
        ok(SUCCEEDED(hr), "%u: failed t oget relative uri, hr %#x.\n", i, hr);

    if (SUCCEEDED(hr))
    {
        hr = IUri_QueryInterface(relative_uri, &IID_IOpcUri, (void **)&unk);
        ok(hr == E_NOINTERFACE, "%u: unexpected hr %#x.\n", i, hr);

        hr = IUri_GetRawUri(relative_uri, &str);
        ok(SUCCEEDED(hr), "%u: failed to get raw uri, hr %#x.\n", i, hr);
        ok(!lstrcmpW(str, relativeW) || broken(relative_broken_W && !lstrcmpW(str, relative_broken_W)),
                "%u: unexpected relative uri %s.\n", i, wine_dbgstr_w(str));
        SysFreeString(str);

        IUri_Release(relative_uri);
    }
        IOpcUri_Release(part_uri);
        IOpcPartUri_Release(combined_uri);

        heap_free(uriW);
        heap_free(combinedW);
        heap_free(relativeW);
        heap_free(relative_broken_W);
    }

    IOpcFactory_Release(factory);
}

static void test_combine_uri(void)
{
    static const struct
    {
        const char *uri;
        const char *relative;
        const char *combined;
    }
    combine_tests[] =
    {
        { "/", "path", "/path" },
        { "/path1", "path2", "/path2" },
        { "/path1", "../path2", "/path2" },
        { "/path1/../path2", "path3", "/path3" },
    };
    IOpcFactory *factory;
    unsigned int i;

    factory = create_factory();

    for (i = 0; i < ARRAY_SIZE(combine_tests); ++i)
    {
        WCHAR *uriW, *relativeW, *combinedW;
        IOpcPartUri *combined_uri;
        IUri *relative_uri;
        IOpcUri *uri;
        HRESULT hr;
        BSTR str;

        uriW = strdupAtoW(combine_tests[i].uri);
        relativeW = strdupAtoW(combine_tests[i].relative);
        combinedW = strdupAtoW(combine_tests[i].combined);

        if (!strcmp(combine_tests[i].uri, "/"))
            hr = IOpcFactory_CreatePackageRootUri(factory, &uri);
        else
            hr = IOpcFactory_CreatePartUri(factory, uriW, (IOpcPartUri **)&uri);
        ok(SUCCEEDED(hr), "%u: failed to create uri, hr %#x.\n", i, hr);

        hr = CreateUri(relativeW, Uri_CREATE_ALLOW_RELATIVE, 0, &relative_uri);
        ok(SUCCEEDED(hr), "%u: failed to create relative uri, hr %#x.\n", i, hr);

        combined_uri = (void *)0xdeadbeef;
        hr = IOpcUri_CombinePartUri(uri, NULL, &combined_uri);
        ok(hr == E_POINTER, "%u: failed to combine uris, hr %#x.\n", i, hr);
        ok(!combined_uri, "Unexpected instance.\n");

        hr = IOpcUri_CombinePartUri(uri, relative_uri, NULL);
        ok(hr == E_POINTER, "%u: failed to combine uris, hr %#x.\n", i, hr);

        hr = IOpcUri_CombinePartUri(uri, relative_uri, &combined_uri);
        ok(SUCCEEDED(hr), "%u: failed to combine uris, hr %#x.\n", i, hr);

        hr = IOpcPartUri_GetRawUri(combined_uri, &str);
        ok(SUCCEEDED(hr), "%u: failed to get raw uri, hr %#x.\n", i, hr);
    todo_wine_if(i == 2 || i == 3)
        ok(!lstrcmpW(str, combinedW), "%u: unexpected uri %s.\n", i, wine_dbgstr_w(str));
        SysFreeString(str);

        IOpcPartUri_Release(combined_uri);

        heap_free(uriW);
        heap_free(relativeW);
        heap_free(combinedW);

        IOpcUri_Release(uri);
        IUri_Release(relative_uri);
    }

    IOpcFactory_Release(factory);
}

static void test_create_part_uri(void)
{
    static const struct
    {
        const char *input;
        const char *raw_uri;
    }
    create_part_uri_tests[] =
    {
        { "path", "/path" },
        { "../path", "/path" },
        { "../../path", "/path" },
        { "/path", "/path" },
        { "/path1/path2/path3/../path4", "/path1/path2/path4" },
    };
    IOpcFactory *factory;
    unsigned int i;
    HRESULT hr;

    factory = create_factory();

    for (i = 0; i < ARRAY_SIZE(create_part_uri_tests); ++i)
    {
        IOpcPartUri *part_uri;
        WCHAR *inputW, *rawW;
        IUri *uri;
        BSTR str;
        BOOL ret;

        inputW = strdupAtoW(create_part_uri_tests[i].input);
        rawW = strdupAtoW(create_part_uri_tests[i].raw_uri);

        hr = IOpcFactory_CreatePartUri(factory, inputW, &part_uri);
        ok(SUCCEEDED(hr), "%u: failed to create part uri, hr %#x.\n", i, hr);

        hr = IOpcPartUri_GetRawUri(part_uri, &str);
        ok(SUCCEEDED(hr), "Failed to get raw uri, hr %#x.\n", hr);
    todo_wine_if(i == 1 || i == 2 || i == 4)
        ok(!lstrcmpW(str, rawW), "%u: unexpected raw uri %s.\n", i, wine_dbgstr_w(str));
        SysFreeString(str);

        hr = CreateUri(rawW, Uri_CREATE_ALLOW_RELATIVE, 0, &uri);
        ok(SUCCEEDED(hr), "Failed to create uri, hr %#x.\n", hr);

        ret = FALSE;
        hr = IOpcPartUri_IsEqual(part_uri, uri, &ret);
        ok(SUCCEEDED(hr), "IsEqual failed, hr %#x.\n", hr);
    todo_wine_if(i == 1 || i == 2 || i == 4)
        ok(!!ret, "%u: unexpected result %d.\n", i, ret);

        IOpcPartUri_Release(part_uri);
        IUri_Release(uri);

        heap_free(inputW);
        heap_free(rawW);
    }

    IOpcFactory_Release(factory);
}

static HRESULT WINAPI custom_package_QueryInterface(IOpcPackage *iface, REFIID iid, void **out)
{
    if (IsEqualIID(iid, &IID_IOpcPackage) || IsEqualIID(iid, &IID_IUnknown))
    {
        *out = iface;
        IOpcPackage_AddRef(iface);
        return S_OK;
    }

    *out = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI custom_package_AddRef(IOpcPackage *iface)
{
    return 2;
}

static ULONG WINAPI custom_package_Release(IOpcPackage *iface)
{
    return 1;
}

static HRESULT WINAPI custom_package_GetPartSet(IOpcPackage *iface, IOpcPartSet **part_set)
{
    return 0x80000001;
}

static HRESULT WINAPI custom_package_GetRelationshipSet(IOpcPackage *iface, IOpcRelationshipSet **relationship_set)
{
    return 0x80000001;
}

static const IOpcPackageVtbl custom_package_vtbl =
{
    custom_package_QueryInterface,
    custom_package_AddRef,
    custom_package_Release,
    custom_package_GetPartSet,
    custom_package_GetRelationshipSet,
};

static void test_write_package(void)
{
    IOpcPackage custom_package = { &custom_package_vtbl };
    IOpcFactory *factory;
    IOpcPackage *package;
    IStream *stream;
    HRESULT hr;

    factory = create_factory();

    hr = IOpcFactory_CreatePackage(factory, &package);
    ok(SUCCEEDED(hr) || broken(hr == E_NOTIMPL) /* Vista */, "Failed to create a package, hr %#x.\n", hr);
    if (FAILED(hr))
    {
        IOpcFactory_Release(factory);
        return;
    }

    hr = IOpcFactory_WritePackageToStream(factory, NULL, OPC_WRITE_FORCE_ZIP32, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    hr = CreateStreamOnHGlobal(NULL, TRUE, &stream);
    ok(SUCCEEDED(hr), "Failed to create a stream, hr %#x.\n", hr);

    hr = IOpcFactory_WritePackageToStream(factory, NULL, OPC_WRITE_FORCE_ZIP32, stream);
    ok(hr == E_POINTER, "Unexpected hr %#x.\n", hr);

    hr = IOpcFactory_WritePackageToStream(factory, &custom_package, OPC_WRITE_FORCE_ZIP32, stream);
    ok(hr == 0x80000001, "Unexpected hr %#x.\n", hr);

    IStream_Release(stream);

    IOpcFactory_Release(factory);
    IOpcPackage_Release(package);
}

START_TEST(opcservices)
{
    IOpcFactory *factory;
    HRESULT hr;

    hr = CoInitialize(NULL);
    ok(SUCCEEDED(hr), "Failed to initialize COM, hr %#x.\n", hr);

    if (!(factory = create_factory())) {
        win_skip("Failed to create IOpcFactory factory.\n");
        CoUninitialize();
        return;
    }

    test_package();
    test_file_stream();
    test_relationship();
    test_rel_part_uri();
    test_part_enumerator();
    test_rels_enumerator();
    test_relative_uri();
    test_combine_uri();
    test_create_part_uri();
    test_write_package();

    IOpcFactory_Release(factory);

    CoUninitialize();
}
